@detiq/crawler 1.1.28 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/crawler.js +9 -0
- package/dist/crawler.js.map +2 -2
- package/dist/failure-analysis-agent.js +2 -1
- package/dist/failure-analysis-agent.js.map +2 -2
- package/dist/job-queue.js +177 -195
- package/dist/job-queue.js.map +2 -2
- package/package.json +1 -1
- package/src/job-queue.test.ts +69 -2
- package/src/job-queue.ts +202 -160
- package/types/job-queue.d.ts +12 -0
package/dist/job-queue.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/job-queue.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * ZeTa-AI Background Job Queue\n *\n * Postgres-backed durable queue. Each job becomes a PlatformJob record.\n * enqueueJob() writes it QUEUED and, for same-process responsiveness, kicks\n * an immediate best-effort claim attempt \u2014 but durability comes from\n * startJobWorkerLoop()'s poll: it atomically claims QUEUED jobs (Postgres\n * `FOR UPDATE SKIP LOCKED`, safe across any number of concurrent worker\n * processes/containers) and requeues jobs whose heartbeat went stale,\n * so a crashed/restarted worker never silently loses a job.\n *\n * CRAWL-type jobs additionally support AWS SQS as an alternate transport\n * (JOB_QUEUE_BACKEND=sqs, see sqs-queue.ts) for horizontally-scaled crawler\n * fleets; every other job type always goes through the DB-backed queue.\n */\n\nimport crypto from 'node:crypto';\nimport { withTenant, prismaAdmin } from '@detiq/database';\nimport { crawlJobsTotal, crawlJobDurationSeconds } from '@detiq/core';\nimport { logger } from './logger.js';\nimport { AppBrain, getProjectStorageConfig, buildArtifactKey, uploadArtifact, getTenantStorageConfig, uploadScreenshotToTenantStorage, uploadFileToTenantStorage, notifyRunCompletion, recordLLMCall, downloadBufferFromTenantStorage, dispatchToTenantAdmins } from '@detiq/app-brain';\n\nasync function notifyAdmins(tenantId: string, data: {\n type: string; title: string; body: string; metadata?: Record<string, unknown>\n}) {\n try {\n await dispatchToTenantAdmins(tenantId, data as any);\n } catch (e) {\n console.error('[notify] failed:', e);\n }\n}\nimport { generateTaxonomySkillFiles, groupScreensWithAI, generateExecutionPlan, refreshTestCasesSkillFile, refreshTestDataSkillFile, refreshTestScriptSkillFile, refreshRequirementsCoverageSkillFile, refreshTestExecutionHistorySkillFile, refreshVisualRegressionHistorySkillFile, refreshLocatorStrategyWithHealingData, refreshFieldCatalogSkillFile, detectFieldsFromHtml, mergeIntoCatalog, generateArtifactIntelligence } from '@detiq/agents';\nimport type { ExecutionStep } from '@detiq/agents';\nimport { crawlProject, domFallbackElements } from './crawler.js';\nimport { httpCrawl } from './http-engine.js';\nimport { hashContent, normalizeText } from './incremental-diff.js';\nimport { discoverProjectUrls } from './sitemap.js';\nimport { executeExecutionStep, executeTestPlan } from './test-executor.js';\nimport { runNativeCrawl } from './native-crawler.js';\nimport { elementsToPlaywrightLocators } from './locator-extractor.js';\nimport { jira, confluence, figma } from '@detiq/connectors';\nimport { decrypt, config as platformConfig, dispatchWebhook, isFeatureEnabled, interpolatePlaceholders } from '@detiq/core';\nimport { createHmac } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport os from 'node:os';\n\n// \u2500\u2500 GAP-14/15: Structured failure classification with per-type backoff \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport enum FailureType {\n TRANSIENT = 'TRANSIENT', // network timeout, rate limit, 429, 503\n INFRASTRUCTURE = 'INFRASTRUCTURE', // DB connection, OOM, worker crash\n LOGICAL = 'LOGICAL', // bad input, business rule violation, 400/422\n FATAL = 'FATAL', // unrecoverable \u2014 bad config, missing secrets\n}\n\ninterface BackoffConfig {\n maxRetries: number;\n baseDelayMs: number;\n multiplier: number;\n maxDelayMs: number;\n}\n\nconst BACKOFF_BY_TYPE: Record<FailureType, BackoffConfig> = {\n [FailureType.TRANSIENT]: { maxRetries: 5, baseDelayMs: 30_000, multiplier: 2, maxDelayMs: 300_000 },\n [FailureType.INFRASTRUCTURE]: { maxRetries: 3, baseDelayMs: 60_000, multiplier: 3, maxDelayMs: 600_000 },\n [FailureType.LOGICAL]: { maxRetries: 1, baseDelayMs: 120_000, multiplier: 1, maxDelayMs: 120_000 },\n [FailureType.FATAL]: { maxRetries: 0, baseDelayMs: 0, multiplier: 1, maxDelayMs: 0 },\n};\n\nexport function classifyFailure(error: string | Error | unknown): FailureType {\n const msg = (error instanceof Error ? error.message : String(error)).toLowerCase();\n\n // Fatal \u2014 bad configuration, missing secrets\n if (msg.includes('master_enc_key') || msg.includes('missing secret') || msg.includes('invalid configuration')) {\n return FailureType.FATAL;\n }\n\n // Infrastructure \u2014 OOM, DB, worker crashes\n if (\n msg.includes('out of memory') || msg.includes('heap out') ||\n msg.includes('connection refused') || msg.includes('econnrefused') ||\n msg.includes('prisma') || msg.includes('database') ||\n msg.includes('worker exited') || msg.includes('sigkill')\n ) {\n return FailureType.INFRASTRUCTURE;\n }\n\n // Logical \u2014 validation, auth, bad input\n if (\n msg.includes('validation') || msg.includes('invalid input') ||\n msg.includes('not found') || msg.includes('unauthorized') ||\n msg.includes('bad request') || msg.includes('400') || msg.includes('422') ||\n msg.includes('403')\n ) {\n return FailureType.LOGICAL;\n }\n\n // Default: TRANSIENT (network, timeout, rate limit, 429, 503)\n return FailureType.TRANSIENT;\n}\n\nfunction computeNextRetryAt(failureType: FailureType, retryCount: number): Date | null {\n const config = BACKOFF_BY_TYPE[failureType];\n if (retryCount >= config.maxRetries) return null; // exceeded max \u2014 go to DLQ\n const delayMs = Math.min(config.baseDelayMs * Math.pow(config.multiplier, retryCount), config.maxDelayMs);\n return new Date(Date.now() + delayMs);\n}\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// RFC 6238 TOTP \u2014 no external dependency, uses Node built-in crypto.\nexport function generateTOTP(secret: string): string {\n const BASE32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';\n const clean = secret.toUpperCase().replace(/\\s/g, '').replace(/=/g, '');\n let bits = 0, value = 0;\n const bytes: number[] = [];\n for (const ch of clean) {\n const idx = BASE32.indexOf(ch);\n if (idx === -1) continue;\n value = (value << 5) | idx;\n bits += 5;\n if (bits >= 8) { bits -= 8; bytes.push((value >> bits) & 0xff); }\n }\n const counter = Math.floor(Date.now() / 30000);\n const buf = Buffer.alloc(8);\n buf.writeBigInt64BE(BigInt(counter));\n const hmac = createHmac('sha1', Buffer.from(bytes)).update(buf).digest();\n const offset = hmac[hmac.length - 1] & 0xf;\n const code = ((hmac[offset] & 0x7f) << 24) | ((hmac[offset + 1] & 0xff) << 16) |\n ((hmac[offset + 2] & 0xff) << 8) | (hmac[offset + 3] & 0xff);\n return String(code % 1_000_000).padStart(6, '0');\n}\n\nfunction deriveFallbackTargetPath(ref: string, framework: string): string {\n const slug = (ref ?? 'test').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'test';\n const base = framework.startsWith('cypress') ? 'cypress/e2e/'\n : (framework === 'playwright-py' || framework === 'pytest' || framework === 'selenium-py') ? 'tests/'\n : (framework === 'playwright-java' || framework === 'selenium-java' || framework === 'junit') ? 'src/test/java/'\n : 'tests/';\n const ext = (framework === 'playwright-py' || framework === 'pytest' || framework === 'selenium-py') ? '_test.py'\n : (framework === 'playwright-java' || framework === 'selenium-java' || framework === 'junit') ? 'Test.java'\n : framework.includes('ts') ? '.spec.ts'\n : '.spec.js';\n return `${base}${slug}${ext}`;\n}\n\nfunction makeJobName(type: string): string {\n const now = new Date();\n const date = now.toISOString().slice(0, 10); // YYYY-MM-DD\n const ts = now.getTime();\n const label = type === 'CRAWL' ? 'Crawl'\n : type === 'CAPTURE_STEPS' ? 'CaptureSteps'\n : type === 'NATIVE_CRAWL' ? 'NativeCrawl'\n : type === 'GENERATE_TESTS' ? 'GenerateTests'\n : type === 'VISUAL_REGRESSION' ? 'VisualRegression'\n : type === 'AI_EDIT' ? 'AIEdit'\n : type === 'API_TESTS' ? 'ApiTests'\n : type === 'SECURITY_TESTS' ? 'SecurityTests'\n : type === 'INTEGRATION_TESTS' ? 'IntegrationTests'\n : type === 'GENERATE_TESTS_FROM_REQS' ? 'GenerateTestsFromReqs'\n : type === 'GENERATE_DATA' ? 'GenerateData'\n : type === 'GENERATE_SCRIPT' ? 'GenerateScript'\n : type === 'GENERATE_TEST_PLAN' ? 'GenerateTestPlan'\n : type === 'IMPORT_TESTS' ? 'ImportTests'\n : type === 'GENERATE_SCREEN_TESTS' ? 'GenerateScreenTests'\n : type === 'BATCH_GENERATE_ALIGNED' ? 'BatchGenerateAligned'\n : type === 'CREATE_PR' ? 'CreatePR'\n : type === 'NEW_PAGES_PIPELINE' ? 'NewPagesPipeline'\n : type.charAt(0) + type.slice(1).toLowerCase().replace(/_([a-z])/g, (_, c) => c.toUpperCase());\n return `${label}_${date}_${ts}`;\n}\n\nexport interface PlatformJobConfig {\n tenantId: string;\n projectId: string;\n appUrl?: string; // Optional since generate jobs don't strictly need it\n credentials?: { username: string; password: string; oneTimeCode?: string };\n /** Decrypted Playwright storageState JSON \u2014 if present, session is restored without AI login. */\n storageState?: string;\n screenshotDir?: string;\n maxScreens?: number;\n /** Seed URLs added to the initial crawl queue \u2014 link discovery still runs from each page. */\n startUrls?: string[];\n /** When provided, crawl ONLY these exact URLs (no link-following). Used for selected-screen recrawl. */\n selectiveUrls?: string[];\n /** Device profiles for multi-viewport screenshot pass after main DESKTOP crawl. */\n deviceProfiles?: string[];\n /** HTTP Basic Auth credentials \u2014 injected as Authorization header on same-origin requests. */\n httpBasicUsername?: string;\n httpBasicPassword?: string;\n /** Bearer/API token \u2014 injected as a request header and into localStorage. Skips AI login. */\n authToken?: string;\n /** Header name for authToken (default: 'Authorization'). */\n authTokenHeader?: string;\n /** Prefix for authToken value (default: 'Bearer '). */\n authTokenPrefix?: string;\n /** CAPTCHA solving API key (2captcha or CapSolver). */\n captchaSolverApiKey?: string;\n /** CAPTCHA solving service provider (default: '2captcha'). */\n captchaSolverProvider?: '2captcha' | 'capsolver';\n /**\n * Path to a Chrome user data directory for profile inheritance (local deployments only).\n * When set, the crawler exports the existing session from this profile and injects it.\n */\n chromeProfilePath?: string;\n /** MailSlurp API key for email OTP auto-retrieval. */\n mailslurpApiKey?: string;\n /** MailSlurp inbox ID to poll for OTP emails. */\n mailslurpInboxId?: string;\n /** SSO provider ('okta' | 'azure_ad' | 'generic_oidc'). */\n ssoProvider?: string;\n /** SSO domain or tenant ID. */\n ssoDomain?: string;\n /** SSO OAuth2 client ID. */\n ssoClientId?: string;\n /** SSO OAuth2 client secret (decrypted). */\n ssoClientSecret?: string;\n /** SSO scopes (space-separated). Default: 'openid profile'. */\n ssoScope?: string;\n /** HTTP/HTTPS/SOCKS5 proxy URL \u2014 routes browser through this proxy for bot-protected sites. */\n proxyUrl?: string;\n // \u2500\u2500\u2500 scrapegraph gap fields \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** HTML extraction mode. 'normal'=raw, 'reader'=Readability article, 'prune'=strip nav/ads. Default: 'normal'. */\n htmlMode?: 'normal' | 'reader' | 'prune';\n /** Per-request screenshot dimensions and quality. Defaults: 1440\u00D7900, quality 80. */\n screenshotConfig?: {\n fullPage?: boolean;\n width?: number;\n height?: number;\n quality?: number;\n };\n /** Enable/disable Playwright stealth plugin. Default: true (existing behaviour). */\n stealth?: boolean;\n /** Mock mode \u2014 intercept all navigation and return synthetic content. Good for CI. */\n mock?: boolean | { minKb?: number; maxKb?: number; minSleep?: number; maxSleep?: number };\n /** MIME type allowlist \u2014 crawler skips URLs returning disallowed content types. */\n allowedTypes?: string[];\n /** Glob-style include patterns for URL paths (e.g. '/blog/*'). Only matching paths crawled. */\n includePatterns?: string[];\n /** Glob-style exclude patterns for URL paths (e.g. '/admin/*'). Matching paths skipped. */\n excludePatterns?: string[];\n /** Allow crawler to follow links to external domains. Default: false. */\n allowExternal?: boolean;\n /** Requested output formats for a single-pass multi-format extraction. */\n requestedFormats?: Array<'markdown' | 'html' | 'links' | 'images' | 'summary' | 'json' | 'branding' | 'screenshot'>;\n /** LLM chunker configuration for AI extraction passes. */\n chunkerConfig?: { size?: number | 'dynamic'; overlap?: number };\n /** ISO-3166-1 alpha-2 country code for geo-targeted proxy routing (e.g. 'us', 'gb'). */\n proxyCountry?: string;\n /** When true, after the primary crawl auto-enqueue one CRAWL job per credentialProfile. */\n crawlAllRoles?: boolean;\n /** Decrypted credential profiles used when crawlAllRoles is true. */\n credentialProfiles?: Array<{ id: string; name: string; username: string; password: string }>;\n /** Set on role-crawl sub-jobs \u2014 profile name tagged on discovered screens via roleAccess column. */\n roleProfileName?: string;\n}\n\n/** In-memory map of jobId \u2192 AbortController for RUNNING jobs. */\nconst runningControllers = new Map<string, AbortController>();\n\n// \u2500\u2500\u2500 Durable job worker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// A minimal Postgres-backed work queue: atomic claim via `FOR UPDATE SKIP\n// LOCKED` (safe for any number of concurrent pollers, in-process or across\n// containers), plus a stale-job sweep keyed off a liveness heartbeat rather\n// than wall-clock job age, so a slow-but-alive job is never requeued and\n// double-processed. See startJobWorkerLoop() below.\n\nconst WORKER_ID = `${os.hostname()}:${process.pid}`;\nconst STALE_JOB_TIMEOUT_MINUTES = Number(process.env.JOB_STALE_TIMEOUT_MINUTES) || 30;\nconst MAX_CONCURRENT_PER_TENANT = Number(process.env.MAX_CONCURRENT_PER_TENANT) || 4;\nconst WORKER_POLL_INTERVAL_MS = Number(process.env.JOB_WORKER_POLL_INTERVAL_MS) || 15_000;\nconst WORKER_CONCURRENCY = Number(process.env.JOB_WORKER_CONCURRENCY) || 8;\n\n// GAP-12: true active-execution semaphore \u2014 counts jobs currently executing\n// (claim\u2192finish), not poll slots. A single poll loop checks this before every\n// claim so the cap is never exceeded even across overlapping ticks.\nlet activeExecutions = 0;\n\nfunction withSemaphore<T>(fn: () => Promise<T>): Promise<T> {\n activeExecutions++;\n return fn().finally(() => { activeExecutions--; });\n}\n\nexport function getActiveExecutionCount(): number {\n return activeExecutions;\n}\n\n// Max simultaneous DISCOVER jobs \u2014 each launches a Playwright browser and holds\n// DB connections, so running many concurrently exhausts resources. Default 2.\nconst MAX_CONCURRENT_DISCOVER = Number(process.env.MAX_CONCURRENT_DISCOVER) || 2;\nconst JOB_HEARTBEAT_INTERVAL_MS = Number(process.env.JOB_HEARTBEAT_INTERVAL_MS) || 15_000;\n// How many headless browsers a single RUN_TEST_SUITE job launches at once \u2014\n// distinct from WORKER_CONCURRENCY (how many *jobs* the poll loop claims at\n// once). Bounded by default to avoid exhausting a container's memory; the\n// primary scale-out path for running more tests at once is still horizontal\n// (more worker containers / SQS), not raising this past what one container\n// can safely hold \u2014 see startJobWorkerLoop's doc comment.\nconst TEST_EXEC_CONCURRENCY = Number(process.env.TEST_EXEC_CONCURRENCY) || 5;\n\ntype ClaimedJob = { id: string; tenantId: string; projectId: string; type: string; config: any };\n\n/** Dispatch table populated at module bottom, once every process*Job function is declared. */\nlet jobDispatchTable: Record<string, (jobId: string, config: any) => Promise<void>> | null = null;\n\n/** Atomically claims one specific QUEUED job for this worker. Returns null if it was already claimed. */\nexport async function claimSpecificJob(jobId: string): Promise<ClaimedJob | null> {\n const rows = await prismaAdmin.$queryRaw<ClaimedJob[]>`\n UPDATE \"PlatformJob\"\n SET status = 'RUNNING', \"startedAt\" = NOW(), \"heartbeatAt\" = NOW(), \"workerId\" = ${WORKER_ID}\n WHERE id = ${jobId} AND status = 'QUEUED'\n AND (\"nextRetryAt\" IS NULL OR \"nextRetryAt\" <= NOW())\n RETURNING id, \"tenantId\" AS \"tenantId\", \"projectId\" AS \"projectId\", type, config\n `;\n return rows[0] ?? null;\n}\n\n/** Atomically claims the oldest QUEUED job across all tenants. SKIP LOCKED makes this\n * race-safe against any number of other pollers doing the same thing concurrently.\n * DISCOVER jobs are subject to MAX_CONCURRENT_DISCOVER \u2014 skipped when that many are\n * already RUNNING to prevent Playwright/DB pool exhaustion from simultaneous crawls. */\nexport async function claimNextQueuedJob(): Promise<ClaimedJob | null> {\n const maxDiscover = MAX_CONCURRENT_DISCOVER;\n const rows = await prismaAdmin.$queryRaw<ClaimedJob[]>`\n UPDATE \"PlatformJob\"\n SET status = 'RUNNING', \"startedAt\" = NOW(), \"heartbeatAt\" = NOW(), \"workerId\" = ${WORKER_ID}\n WHERE id = (\n SELECT id FROM \"PlatformJob\" p\n WHERE status = 'QUEUED'\n AND (\"nextRetryAt\" IS NULL OR \"nextRetryAt\" <= NOW())\n AND (\n type != 'DISCOVER'\n OR (SELECT COUNT(*) FROM \"PlatformJob\" WHERE status = 'RUNNING' AND type = 'DISCOVER') < ${maxDiscover}\n )\n AND (\n SELECT COUNT(*) FROM \"PlatformJob\" p2\n WHERE p2.status = 'RUNNING'\n AND p2.\"tenantId\" = p.\"tenantId\"\n AND p2.inline = false\n ) < ${MAX_CONCURRENT_PER_TENANT}\n ORDER BY p.\"priority\" DESC, p.\"queuedAt\" ASC\n FOR UPDATE SKIP LOCKED\n LIMIT 1\n )\n RETURNING id, \"tenantId\" AS \"tenantId\", \"projectId\" AS \"projectId\", type, config\n `;\n return rows[0] ?? null;\n}\n\n/** Requeues RUNNING jobs whose heartbeat has gone stale \u2014 the worker that claimed them\n * died (crash, restart, OOM) without ever marking them DONE/FAILED. Idempotent and\n * safe to run from every worker process; only affects rows past the timeout.\n * API-in-process job types (GENERATE_SCRIPT etc.) are failed directly \u2014 requeueing them\n * would cause the worker to claim and fail them with a confusing \"claimed by worker\" error. */\nexport async function requeueStaleRunningJobs(): Promise<void> {\n // Directly fail stale API-in-process jobs \u2014 worker cannot process these types.\n const failedRows = await prismaAdmin.$queryRaw<Array<{ id: string }>>`\n UPDATE \"PlatformJob\"\n SET status = 'FAILED',\n \"finishedAt\" = NOW(),\n error = 'API process was restarted while this job was running \u2014 please retry from the UI.'\n WHERE status = 'RUNNING'\n AND type IN ('GENERATE_SCRIPT', 'GENERATE_DATA', 'GENERATE_TESTS', 'GENERATE_TEST_PLAN')\n AND COALESCE(\"heartbeatAt\", \"startedAt\") < NOW() - (${STALE_JOB_TIMEOUT_MINUTES} * interval '1 minute')\n RETURNING id\n `;\n if (failedRows.length > 0) {\n logger.warn({ jobIds: failedRows.map((r: any) => r.id), staleTimeoutMinutes: STALE_JOB_TIMEOUT_MINUTES }, '[job-worker] failed stale API-in-process job(s) \u2014 API restarted while running');\n }\n\n const rows = await prismaAdmin.$queryRaw<Array<{ id: string }>>`\n UPDATE \"PlatformJob\"\n SET status = 'QUEUED',\n \"workerId\" = NULL,\n result = jsonb_set(\n COALESCE(result::jsonb, '{}'::jsonb),\n '{step}',\n to_jsonb('Worker heartbeat lost \u2014 queued for recovery'::text),\n true\n )\n WHERE status = 'RUNNING'\n AND type NOT IN ('GENERATE_SCRIPT', 'GENERATE_DATA', 'GENERATE_TESTS', 'GENERATE_TEST_PLAN')\n AND COALESCE(\"heartbeatAt\", \"startedAt\") < NOW() - (${STALE_JOB_TIMEOUT_MINUTES} * interval '1 minute')\n RETURNING id\n `;\n if (rows.length > 0) {\n logger.warn({ jobIds: rows.map((r: any) => r.id), staleTimeoutMinutes: STALE_JOB_TIMEOUT_MINUTES }, '[job-worker] requeued stale RUNNING job(s)');\n }\n}\n\n/**\n * Requeues any RUNNING jobs claimed by a different worker (i.e., a previous\n * process instance). Call once at startup before the poll loop begins \u2014 this\n * provides immediate recovery instead of waiting the full STALE_JOB_TIMEOUT_MINUTES.\n * Safe to call from multiple replicas simultaneously (each uses its own WORKER_ID).\n */\nexport async function requeueOrphanedJobs(): Promise<void> {\n const rows = await prismaAdmin.$queryRaw<Array<{ id: string }>>`\n UPDATE \"PlatformJob\"\n SET status = 'QUEUED',\n \"workerId\" = NULL,\n result = jsonb_set(\n COALESCE(result::jsonb, '{}'::jsonb),\n '{step}',\n to_jsonb('Worker restarted \u2014 queued for recovery'::text),\n true\n )\n WHERE status = 'RUNNING'\n AND \"workerId\" IS NOT NULL\n AND \"workerId\" != ${WORKER_ID}\n RETURNING id\n `;\n if (rows.length > 0) {\n logger.warn({ jobIds: rows.map((r: any) => r.id), workerId: WORKER_ID }, '[job-worker] requeued orphaned RUNNING job(s) from dead worker');\n }\n}\n\n/** Dispatches a claimed job to its type's processor. Last-resort safety net \u2014 every\n * process*Job function already catches and records its own failures. */\nexport async function dispatchClaimedJob(job: ClaimedJob): Promise<void> {\n const dispatcher = jobDispatchTable?.[job.type];\n if (!dispatcher) {\n logger.error({ jobId: job.id, type: job.type }, '[job-worker] no dispatcher registered for job type');\n await prismaAdmin.platformJob.update({\n where: { id: job.id },\n data: { status: 'FAILED', error: `Unknown job type: ${job.type}`, finishedAt: new Date() },\n }).catch(() => { });\n return;\n }\n const stopTimer = crawlJobDurationSeconds.startTimer({ type: job.type });\n try {\n await dispatcher(job.id, job.config);\n stopTimer();\n // dispatcher() resolving doesn't mean the job succeeded \u2014 every process*Job\n // function catches and records its own failures internally without\n // rethrowing (see this function's doc comment). Read back the actual\n // final status so the metric reflects reality, not just \"didn't throw\".\n const finished = await prismaAdmin.platformJob.findUnique({ where: { id: job.id }, select: { status: true } }).catch(() => null);\n crawlJobsTotal.inc({ type: job.type, status: finished?.status === 'FAILED' ? 'failed' : 'success' });\n } catch (err: any) {\n stopTimer();\n crawlJobsTotal.inc({ type: job.type, status: 'failed' });\n logger.error({ jobId: job.id, type: job.type, err: String(err?.message ?? err) }, '[job-worker] job threw uncaught error');\n const errorMessage = String(err?.message ?? err);\n const failureType = classifyFailure(err);\n // Read current retryCount before incrementing so computeNextRetryAt uses the right base.\n const jobRow = await prismaAdmin.platformJob.findUnique({ where: { id: job.id }, select: { retryCount: true, result: true } }).catch(() => null);\n const currentRetry = jobRow?.retryCount ?? 0;\n const nextRetryAt = computeNextRetryAt(failureType, currentRetry);\n await prismaAdmin.platformJob.update({\n where: { id: job.id },\n data: {\n status: nextRetryAt ? 'RETRY_WAIT' : 'FAILED',\n error: errorMessage,\n finishedAt: new Date(),\n heartbeatAt: null,\n workerId: null,\n nextRetryAt,\n retryCount: { increment: 1 },\n failureType,\n // Store failureType in result JSON for observability (backward compat)\n result: { ...(jobRow?.result as any ?? {}), failureType } as any,\n },\n }).catch(() => { });\n // If fatal or exceeded retries \u2014 promote to DLQ immediately\n if (!nextRetryAt || failureType === FailureType.FATAL) {\n await prismaAdmin.platformJob.update({\n where: { id: job.id },\n data: { dlq: true, dlqAt: new Date(), dlqReason: errorMessage },\n }).catch(() => { });\n }\n }\n}\n\nlet workerLoopTimer: ReturnType<typeof setInterval> | null = null;\n// GAP-12: signals the semaphore-based poll loop to exit gracefully.\nlet workerShuttingDown = false;\n\n/** Re-promotes FAILED jobs to RETRY_WAIT if they are eligible for retry based on per-type limits.\n * RETRY_WAIT makes the waiting period visible in the UI. A separate sweep then promotes\n * RETRY_WAIT \u2192 QUEUED once nextRetryAt <= NOW(). */\nexport async function retryEligibleFailedJobs(): Promise<void> {\n // Legacy path: jobs that failed before failure-type classification was added.\n // New failures go FAILED\u2192RETRY_WAIT directly in the job error handler (dispatchClaimedJob).\n // This only picks up old FAILED rows that have no nextRetryAt set yet.\n // Phase 1: FAILED \u2192 RETRY_WAIT (GAP-07: visible waiting state)\n const rows = await prismaAdmin.$queryRaw<Array<{ id: string }>>`\n UPDATE \"PlatformJob\"\n SET status = 'RETRY_WAIT',\n \"retryCount\" = \"retryCount\" + 1,\n \"nextRetryAt\" = NOW() + (INTERVAL '1 minute' * POWER(2, \"retryCount\")),\n \"workerId\" = NULL\n WHERE status = 'FAILED'\n AND dlq = false\n AND \"nextRetryAt\" IS NULL\n AND \"retryCount\" < 5\n RETURNING id\n `;\n if (rows.length > 0) {\n logger.info({ jobIds: rows.map((r: any) => r.id) }, '[job-worker] moved failed job(s) to RETRY_WAIT');\n }\n\n // Phase 2: RETRY_WAIT \u2192 QUEUED when the wait period has elapsed (GAP-07)\n const readyRows = await prismaAdmin.$queryRaw<Array<{ id: string }>>`\n UPDATE \"PlatformJob\"\n SET status = 'QUEUED',\n \"requeuedAt\" = NOW()\n WHERE status = 'RETRY_WAIT'\n AND \"nextRetryAt\" <= NOW()\n AND dlq = false\n RETURNING id\n `;\n if (readyRows.length > 0) {\n logger.info({ jobIds: readyRows.map((r: any) => r.id) }, '[job-worker] promoted RETRY_WAIT job(s) to QUEUED');\n }\n}\n\n/** Promotes FAILED jobs to DLQ if they exceed their per-type retry limits. */\nexport async function promoteToDeadLetterQueue(): Promise<void> {\n const rows = await prismaAdmin.$queryRaw<Array<{ id: string }>>`\n UPDATE \"PlatformJob\"\n SET dlq = true,\n \"dlqAt\" = NOW(),\n \"dlqReason\" = error\n WHERE status = 'FAILED'\n AND dlq = false\n AND \"retryCount\" >= CASE\n WHEN type IN ('CRAWL', 'NATIVE_CRAWL') THEN 3\n WHEN type IN ('GENERATE_TESTS', 'IMPORT_TESTS', 'RUN_TEST_SUITE', 'VISUAL_REGRESSION') THEN 2\n WHEN type IN ('GENERATE_SCRIPT', 'GENERATE_DATA', 'GENERATE_TEST_PLAN') THEN 0\n ELSE 1\n END\n RETURNING id\n `;\n if (rows.length > 0) {\n logger.error({ jobIds: rows.map((r: any) => r.id) }, '[job-worker] promoted failed job(s) to DLQ');\n }\n}\n\n/**\n * Starts the durable job worker poll loop. This is what makes job processing\n * survive a restart \u2014 enqueueJob()'s immediate same-process claim attempt is\n * only a latency optimization; if the process dies before that fires (or\n * mid-job), this loop's next tick claims/requeues it. Call once at process\n * startup (API server and/or a dedicated crawler worker process \u2014 safe to\n * run in both, and safe to run in many container replicas at once).\n */\nexport function startJobWorkerLoop(opts?: { pollIntervalMs?: number; concurrency?: number }): void {\n if (workerLoopTimer) return; // already running in this process\n const pollIntervalMs = opts?.pollIntervalMs ?? WORKER_POLL_INTERVAL_MS;\n const concurrency = opts?.concurrency ?? WORKER_CONCURRENCY;\n logger.info({ workerId: WORKER_ID, pollIntervalMs, concurrency }, '[job-worker] starting');\n\n // GAP-12: maintenance sweep stays on a fixed interval (unchanged).\n const sweepTick = async () => {\n try {\n await retryEligibleFailedJobs();\n await promoteToDeadLetterQueue();\n await requeueStaleRunningJobs();\n } catch (err) {\n logger.error({ err: String(err) }, '[job-worker] stale/retry job sweep failed');\n }\n };\n workerLoopTimer = setInterval(sweepTick, pollIntervalMs);\n\n // GAP-12: single continuous poll loop \u2014 checks the semaphore before every\n // claim so activeExecutions never exceeds WORKER_CONCURRENCY regardless of\n // how long individual jobs take. Jobs are non-blocking: withSemaphore(...)\n // is NOT awaited here, so the loop immediately loops back and can claim the\n // next job while previous ones are still running.\n //\n // NOTE (GAP-20): requeueOrphanedJobs() is intentionally NOT called at startup here.\n // Calling it at startup is unsafe in multi-replica deployments \u2014 replica A would steal\n // RUNNING jobs from replica B (which is still alive and processing them), causing\n // double-execution. The stale-job sweep (requeueStaleRunningJobs) handles recovery\n // safely via heartbeat timeout. requeueOrphanedJobs() remains available for admin use.\n workerShuttingDown = false;\n\n const pollLoop = async () => {\n void sweepTick(); // run one sweep immediately on startup\n while (!workerShuttingDown) {\n if (activeExecutions >= concurrency) {\n // All slots busy \u2014 wait before checking again without hammering the DB.\n await new Promise(resolve => setTimeout(resolve, 500));\n continue;\n }\n\n let paused = false;\n try {\n const pauseRow = await prismaAdmin.systemConfig.findUnique({ where: { key: 'queue.paused' } });\n paused = pauseRow?.value === 'true';\n } catch {\n // ignore \u2014 treat as unpaused\n }\n\n if (paused) {\n logger.info('[job-worker] queue is paused \u2014 skipping claim');\n await new Promise(resolve => setTimeout(resolve, pollIntervalMs));\n continue;\n }\n\n let job: ClaimedJob | null = null;\n try {\n job = await claimNextQueuedJob();\n } catch (err) {\n logger.error({ err: String(err) }, '[job-worker] claim failed');\n }\n\n if (!job) {\n // Nothing queued \u2014 back off before polling again.\n await new Promise(resolve => setTimeout(resolve, pollIntervalMs));\n continue;\n }\n\n // Non-blocking: fire and forget into semaphore \u2014 poll loop continues immediately.\n withSemaphore(() => dispatchClaimedJob(job!)).catch(err => {\n logger.error({ err, jobId: job!.id }, '[worker] unhandled job error');\n });\n }\n };\n\n void pollLoop();\n}\n\n/** Stops the poll loop. Mainly for tests. */\nexport function stopJobWorkerLoop(): void {\n workerShuttingDown = true; // GAP-12: signal continuous poll loop to exit\n if (workerLoopTimer) clearInterval(workerLoopTimer);\n workerLoopTimer = null;\n}\n\n/** Create a job record that tracks a synchronous operation (telemetry only, no background processor). */\nexport async function createTrackedJob(\n type: string,\n config: { tenantId: string; projectId: string;[k: string]: any }\n): Promise<string> {\n const name = makeJobName(type);\n const job = await withTenant(config.tenantId, (tx) =>\n tx.platformJob.create({\n data: { tenantId: config.tenantId, projectId: config.projectId, type, name, status: 'RUNNING', startedAt: new Date(), inline: true, config: config as any },\n })\n ) as { id: string };\n return job.id;\n}\n\n/** Mark a tracked job DONE or FAILED. Safe to call in finally \u2014 swallows errors.\n * GAP-14/15: Uses failure-type classification for per-type backoff on FAILED. */\nexport async function finishTrackedJob(tenantId: string, jobId: string, error?: string): Promise<void> {\n if (!error) {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', finishedAt: new Date(), progress: 100 } })\n ).catch(() => { });\n return;\n }\n const failureType = classifyFailure(error);\n const jobRow = await prismaAdmin.platformJob.findUnique({ where: { id: jobId }, select: { retryCount: true, result: true } }).catch(() => null);\n const currentRetry = jobRow?.retryCount ?? 0;\n const nextRetryAt = computeNextRetryAt(failureType, currentRetry);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: nextRetryAt ? 'RETRY_WAIT' : 'FAILED',\n finishedAt: new Date(),\n error,\n nextRetryAt,\n retryCount: { increment: 1 },\n failureType,\n result: { ...(jobRow?.result as any ?? {}), failureType } as any,\n },\n })\n ).catch(() => { });\n if (!nextRetryAt || failureType === FailureType.FATAL) {\n await prismaAdmin.platformJob.update({\n where: { id: jobId },\n data: { dlq: true, dlqAt: new Date(), dlqReason: error },\n }).catch(() => { });\n }\n}\n\n/** Enqueue a generic platform job \u2014 returns the job ID immediately. */\nexport async function enqueueJob(type: string, config: any): Promise<string> {\n const { tenantId, projectId, submittedBy } = config;\n\n const name = makeJobName(type);\n const job = await withTenant(tenantId, (tx) =>\n tx.platformJob.create({\n data: {\n tenantId,\n projectId,\n type,\n name,\n status: 'QUEUED',\n config: config as any,\n ...(submittedBy ? { submittedBy } : {}),\n },\n })\n ) as { id: string };\n\n // Durability comes from startJobWorkerLoop()'s poll \u2014 this is only a same-process\n // latency optimization so a job doesn't sit idle until the next poll tick. The\n // claim is atomic (WHERE status='QUEUED'), so it's safe even if the poll loop's\n // own tick fires concurrently and claims this same job first.\n if (process.env.JOB_QUEUE_BACKEND === 'sqs') {\n const { sendJobToSqs } = await import('./sqs-queue.js');\n await sendJobToSqs(job.id);\n } else if (process.env.DISABLE_WORKER !== 'true') {\n // Skip inline dispatch when DISABLE_WORKER=true \u2014 the dedicated crawler process\n // will pick this up via its poll loop. Inline dispatch in the API container\n // would launch Playwright browsers and crash the API.\n setImmediate(() => claimSpecificJob(job.id).then((claimed) => claimed && dispatchClaimedJob(claimed)));\n }\n\n return job.id;\n}\n\n/** Get the current state of a platform job. */\nexport async function getJob(tenantId: string, jobId: string) {\n return withTenant(tenantId, (tx) =>\n tx.platformJob.findFirst({ where: { id: jobId, tenantId } })\n );\n}\n\n/** List recent jobs for a project. */\nexport async function listJobs(tenantId: string, projectId: string) {\n return withTenant(tenantId, async (tx) => {\n const jobs = await tx.platformJob.findMany({\n where: { tenantId, projectId },\n orderBy: { queuedAt: 'desc' },\n take: 50,\n });\n // Attach the most-recently-used model for each job from LLMCallLog\n const jobIds = jobs.map((j) => j.id);\n if (jobIds.length === 0) return jobs;\n const modelRows = await (tx as any).lLMCallLog.groupBy({\n by: ['jobId'],\n where: { tenantId, jobId: { in: jobIds } },\n _max: { model: true, calledAt: true },\n });\n const modelByJobId = new Map<string, string>(\n modelRows\n .filter((r: any) => r.jobId && r._max?.model)\n .map((r: any) => [r.jobId as string, r._max.model as string])\n );\n return jobs.map((j) => ({ ...j, llmModel: modelByJobId.get(j.id) ?? null }));\n });\n}\n\n/** Cancel a RUNNING or QUEUED job.\n * Sets status to CANCEL_REQUESTED (GAP-08) so the worker's abort handler can\n * detect the request and set CANCELLED + finishedAt after the underlying work stops.\n * The in-process AbortController is also triggered for same-process jobs. */\nexport async function cancelJob(tenantId: string, jobId: string): Promise<void> {\n // Abort in-memory controller if job is running in the same process\n const controller = runningControllers.get(jobId);\n if (controller) {\n controller.abort();\n runningControllers.delete(jobId);\n }\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'CANCEL_REQUESTED' },\n })\n );\n}\n\n/** Re-enqueue a job using the same config as a previous job. */\nexport async function rerunJob(tenantId: string, jobId: string): Promise<string> {\n const job = await withTenant(tenantId, (tx) =>\n tx.platformJob.findUnique({ where: { id: jobId } })\n ) as { type: string; config: any } | null;\n if (!job) throw new Error('Job not found');\n return enqueueJob(job.type, { ...(job.config as any), tenantId });\n}\n\n// In-memory checkpoint store: updated every 10 screens, saved to DB result on failure.\nconst jobCheckpoints = new Map<string, { visitedUrls: string[]; pendingQueue: string[] }>();\n\nexport async function resumeJob(tenantId: string, jobId: string): Promise<string> {\n const job = await withTenant(tenantId, (tx) =>\n tx.platformJob.findUnique({ where: { id: jobId } })\n ) as { type: string; config: any; result: any } | null;\n if (!job) throw new Error('Job not found');\n\n const checkpoint = (job.result as any)?.checkpoint;\n const visitedUrls: string[] = checkpoint?.visitedUrls ?? [];\n const pendingQueue: string[] = checkpoint?.pendingQueue ?? [];\n\n if (pendingQueue.length === 0 && visitedUrls.length === 0) {\n // No checkpoint saved \u2014 full rerun\n return enqueueJob(job.type, { ...(job.config as any), tenantId });\n }\n\n return enqueueJob(job.type, {\n ...(job.config as any),\n tenantId,\n skipUrls: visitedUrls.length > 0 ? visitedUrls : undefined,\n resumeUrls: pendingQueue.length > 0 ? pendingQueue : undefined,\n });\n}\n\n/** Update progress percentage and current step label on a running job (non-fatal). */\nexport async function updateJobProgress(tenantId: string, jobId: string, progress: number, step: string) {\n try {\n await withTenant(tenantId, async (tx) => {\n // DB-flag cancellation: works for both inline and SQS backends.\n // When a user cancels, status is set to CANCEL_REQUESTED (GAP-08).\n // This check fires at each progress update (every few seconds during crawl).\n const current = await tx.platformJob.findUnique({\n where: { id: jobId },\n select: { status: true },\n });\n if (current?.status === 'CANCELLED' || current?.status === 'CANCEL_REQUESTED') {\n runningControllers.get(jobId)?.abort();\n runningControllers.delete(jobId);\n return; // abort signal propagates; crawlProject exits on next signal check\n }\n await tx.platformJob.update({\n where: { id: jobId },\n // GAP-09: write currentStep as a proper column in addition to result.step (backward compat)\n data: { progress, currentStep: step, result: { step } as any, heartbeatAt: new Date() },\n });\n });\n } catch { /* non-fatal \u2014 progress updates best-effort */ }\n}\n\n/** Bump only worker liveness. This intentionally does not touch progress/result. */\nasync function heartbeatJob(tenantId: string, jobId: string) {\n try {\n await withTenant(tenantId, async (tx) => {\n const current = await tx.platformJob.findUnique({\n where: { id: jobId },\n select: { status: true },\n });\n if (current?.status === 'CANCELLED' || current?.status === 'CANCEL_REQUESTED') {\n runningControllers.get(jobId)?.abort();\n runningControllers.delete(jobId);\n return;\n }\n if (current?.status !== 'RUNNING') return;\n await tx.platformJob.update({\n where: { id: jobId },\n data: { heartbeatAt: new Date(), workerId: WORKER_ID },\n });\n });\n } catch { /* non-fatal \u2014 heartbeat updates best-effort */ }\n}\n\n/** Background: for each changed screen, search Jira + Confluence for related tickets/pages. */\nasync function investigateScreenChanges(\n tenantId: string,\n projectId: string,\n changedScreens: Array<{ id: string; name: string; url?: string; addedElements: string[]; removedElements: string[] }>\n) {\n const [jiraCfg, confluenceCfg] = await Promise.all([\n AppBrain.getConnectorConfig(tenantId, projectId, 'JIRA'),\n AppBrain.getConnectorConfig(tenantId, projectId, 'CONFLUENCE'),\n ]);\n\n for (const screen of changedScreens) {\n const searchTerm = screen.url ?? screen.name;\n const changeSummary = `Screen \"${screen.name}\" changed: +${screen.addedElements.length} elements, -${screen.removedElements.length} elements`;\n\n const detail: Record<string, any> = {\n screenId: screen.id,\n screenName: screen.name,\n url: screen.url,\n addedElements: screen.addedElements,\n removedElements: screen.removedElements,\n jiraTickets: [],\n confluencePages: [],\n };\n\n if (jiraCfg?.baseUrl && jiraCfg?.email && jiraCfg?.apiToken) {\n try {\n detail.jiraTickets = await jira.searchIssues(\n { baseUrl: jiraCfg.baseUrl, email: jiraCfg.email, apiToken: jiraCfg.apiToken, projectKey: jiraCfg.projectKey },\n searchTerm\n );\n } catch (e) {\n logger.warn({ searchTerm, err: e }, 'Jira search failed');\n }\n }\n\n if (confluenceCfg?.baseUrl && confluenceCfg?.email && confluenceCfg?.apiToken) {\n try {\n detail.confluencePages = await confluence.searchPages(\n { baseUrl: confluenceCfg.baseUrl, email: confluenceCfg.email, apiToken: confluenceCfg.apiToken, spaceKey: confluenceCfg.spaceKey },\n searchTerm\n );\n } catch (e) {\n logger.warn({ searchTerm, err: e }, 'Confluence search failed');\n }\n }\n\n await AppBrain.logChange(tenantId, projectId, 'MODIFIED', changeSummary, detail);\n logger.info({ screen: screen.name, jiraTickets: detail.jiraTickets.length, confluencePages: detail.confluencePages.length }, 'Logged screen change investigation');\n }\n}\n\nexport interface ProcessJobOpts {\n onProgress?: (pct: number) => Promise<void>;\n}\n\nexport async function processJob(jobId: string, config: any, opts?: ProcessJobOpts) {\n const { tenantId, projectId } = config;\n\n // Mark as RUNNING \u2014 skip if already CANCELLED (race: user cancelled before worker picked it up).\n const jobStartedAt = new Date();\n const claimed = await withTenant(tenantId, (tx) =>\n tx.platformJob.updateMany({\n where: { id: jobId, status: { not: 'CANCELLED' } },\n data: { status: 'RUNNING', startedAt: jobStartedAt, heartbeatAt: jobStartedAt, workerId: WORKER_ID },\n })\n );\n if (claimed.count === 0) return;\n\n const controller = new AbortController();\n runningControllers.set(jobId, controller);\n const heartbeatTimer = setInterval(() => {\n void heartbeatJob(tenantId, jobId);\n }, JOB_HEARTBEAT_INTERVAL_MS);\n\n const onProgress = async (pct: number, step: string) => {\n await updateJobProgress(tenantId, jobId, pct, step);\n await opts?.onProgress?.(pct);\n };\n\n const DEFAULT_MAX_SCREENS = parseInt(process.env.CRAWL_DEFAULT_MAX_SCREENS ?? '') || 150;\n\n try {\n // Per-project maxScreens + deviceProfiles + seedUrls + interactive probing + sitemap seeding + traversal strategy from crawlConfig override job config defaults.\n if (!config.maxScreens || !config.deviceProfiles || config.interactive === undefined || config.useSitemap === undefined || !config.traversalStrategy) {\n const proj: any = await withTenant(tenantId, (tx) =>\n (tx as any).project.findUnique({ where: { id: projectId }, select: { crawlConfig: true } })\n );\n const perProject = proj?.crawlConfig?.maxScreens;\n if (!config.maxScreens) config.maxScreens = perProject || DEFAULT_MAX_SCREENS;\n if (!config.deviceProfiles) config.deviceProfiles = proj?.crawlConfig?.deviceProfiles ?? ['DESKTOP'];\n if (config.interactive === undefined) config.interactive = proj?.crawlConfig?.interactive ?? false;\n if (config.useSitemap === undefined) config.useSitemap = proj?.crawlConfig?.useSitemap ?? false;\n if (!config.sitemapUrl) config.sitemapUrl = proj?.crawlConfig?.sitemapUrl ?? undefined;\n if (!config.traversalStrategy) config.traversalStrategy = proj?.crawlConfig?.traversalStrategy ?? 'BFS';\n if (!config.bestFirstKeywords) config.bestFirstKeywords = proj?.crawlConfig?.bestFirstKeywords ?? undefined;\n // segmentSize from crawl config \u2014 default unlimited (9_999_999) so normal sites run in one job;\n // only segment if the project explicitly sets a segmentSize in its crawl config.\n if (!config.segmentSize) config.segmentSize = proj?.crawlConfig?.segmentSize ?? 9_999_999;\n // Load saved seedUrls as non-selective seeds (link discovery still runs from them).\n // Skip when a selective recrawl is requested (selectiveUrls set) or seeds already provided.\n if (!config.selectiveUrls?.length && (!config.startUrls || config.startUrls.length === 0)) {\n const saved: string[] = proj?.crawlConfig?.seedUrls ?? [];\n if (saved.length > 0) config.startUrls = saved;\n }\n if (config.interactive === undefined) config.interactive = proj?.crawlConfig?.interactive ?? false;\n }\n // Build runtime credentials: convert stored totpSecret \u2192 getOneTimeCode function\n const runtimeCreds = config.credentials\n ? {\n ...config.credentials,\n ...(config.credentials.totpSecret\n ? { getOneTimeCode: async () => generateTOTP(config.credentials.totpSecret) }\n : {}),\n }\n : undefined;\n\n const storageConfigForCrawl = await getTenantStorageConfig(tenantId).catch(() => null);\n const result = await crawlProject({\n ...config, credentials: runtimeCreds, abortSignal: controller.signal, jobId,\n storageConfig: storageConfigForCrawl,\n onCheckpoint: async (visitedUrls, pendingQueue) => {\n jobCheckpoints.set(jobId, { visitedUrls, pendingQueue });\n // GAP-19: configurable checkpoint interval; GAP-04: persist to DB for crash recovery\n const CHECKPOINT_INTERVAL = parseInt(process.env.CRAWL_CHECKPOINT_INTERVAL ?? '10');\n if (visitedUrls.length % CHECKPOINT_INTERVAL === 0) {\n try {\n await prismaAdmin.platformJob.update({\n where: { id: jobId },\n data: {\n checkpoint: { visitedUrls, pendingQueue } as any,\n checkpointAt: new Date(),\n },\n });\n } catch { /* non-fatal */ }\n }\n },\n }, onProgress);\n jobCheckpoints.delete(jobId);\n\n const pendingUrls: string[] = (result as any)?.pendingUrls ?? [];\n let nextJobId: string | undefined;\n if (pendingUrls.length > 0) {\n // visitedUrls includes all attempted URLs (screens, soft-404s, API paths) \u2014 more complete than\n // discovered-with-screenId only, preventing the continuation job from re-attempting dead ends.\n const visitedForSkip: string[] = (result as any).visitedUrls ?? ((result as any).discovered ?? [])\n .filter((d: any) => !!d.screenId && !!d.url)\n .map((d: any) => { try { const _u = new URL(d.url as string); _u.hash = ''; return _u.href; } catch { return d.url as string; } });\n const reason = (result as any).wasTimeout ? 'timeout' : 'segment-limit';\n logger.info({ pendingUrls: pendingUrls.length, visited: visitedForSkip.length, reason }, '[crawl] auto-enqueuing continuation job');\n nextJobId = await enqueueJob('CRAWL', {\n ...config,\n startUrls: pendingUrls,\n skipUrls: [...(config.skipUrls ?? []), ...visitedForSkip],\n }).catch((autoErr: any) => { logger.warn({ err: autoErr?.message }, '[crawl] auto-segment enqueue failed (non-fatal)'); return undefined; });\n }\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE',\n result: { ...(result as any), ...(nextJobId ? { nextJobId } : {}) } as any,\n progress: 100,\n finishedAt: new Date(),\n },\n })\n );\n clearInterval(heartbeatTimer);\n runningControllers.delete(jobId);\n\n {\n // Auto-seed: merge all successfully crawled URLs into crawlConfig.seedUrls so future\n // crawls and discovery agents start from a complete picture of the app.\n const crawledUrls: string[] = ((result as any).discovered ?? [])\n .filter((d: any) => !!d.screenId && !!d.url)\n .map((d: any) => d.url as string);\n if (crawledUrls.length > 0) {\n (async () => {\n const cfg = await AppBrain.getCrawlConfig(tenantId, projectId);\n const existing: string[] = cfg?.seedUrls ?? [];\n const merged = Array.from(new Set([...existing, ...crawledUrls]));\n if (merged.length > existing.length) {\n await AppBrain.saveCrawlConfig(tenantId, projectId, { seedUrls: merged });\n logger.info({ added: merged.length - existing.length, total: merged.length }, '[crawl] auto-seeded crawled URLs into crawlConfig.seedUrls');\n }\n })().catch((err: any) => logger.warn({ err: err?.message }, '[crawl] auto-seed seedUrls failed (non-fatal)'));\n }\n\n notifyAdmins(tenantId, {\n type: 'crawl.done',\n title: 'Crawl completed',\n body: `Crawl for project finished successfully.`,\n metadata: { projectId, jobId },\n }).catch(console.error);\n\n dispatchWebhook(tenantId, projectId, 'crawl.done', {\n jobId,\n screensFound: (result as any)?.screensFound ?? (result as any)?.screens?.length ?? 0,\n }).catch(console.error);\n }\n\n // Aggregate token usage from LLM calls made during this job.\n // Delay 8s to let concurrent recordLLMCall writes settle before querying LLMCallLog.\n setTimeout(() => {\n aggregateJobTokens(tenantId, jobId, projectId, jobStartedAt).catch(console.error);\n }, 8000);\n\n // Post-crawl: generate / refresh all taxonomy skill files + investigate changes.\n setImmediate(async () => {\n try {\n const projectMap = await AppBrain.getProjectMap(tenantId, projectId);\n if (projectMap?.screens?.length) {\n // Mark project oversized when > 300 screens \u2014 activates VectorSearchGrounding\n const screenCount = (projectMap.screens as any[]).length;\n if (screenCount > 300) {\n await withTenant(tenantId, (tx) =>\n (tx as any).project.update({ where: { id: projectId }, data: { oversized: true } })\n );\n }\n await generateTaxonomySkillFiles(tenantId, projectId, projectMap.screens as any, result.apiEndpoints, result.performanceMetrics);\n\n // Auto-detect AppField catalog from crawled DOM snapshots.\n // Runs before test generation so getFieldsForCase() returns typed fields (FORM mode).\n // Capped at 20 screens and only where domSnapshotPath exists to bound LLM cost.\n try {\n const { readFile } = await import('node:fs/promises');\n const { existsSync } = await import('node:fs');\n const screensWithDom = (projectMap.screens as any[]).filter((s: any) => s.domSnapshotPath);\n const detectLimit = Math.min(screensWithDom.length, 20);\n let fieldDetectAdded = 0;\n for (let si = 0; si < detectLimit; si++) {\n const screen = screensWithDom[si];\n try {\n let htmlContent: string | null = null;\n const domPath: string = screen.domSnapshotPath;\n if (domPath.startsWith('https://') || domPath.startsWith('http://')) {\n const res = await fetch(domPath);\n if (res.ok) htmlContent = await res.text();\n } else if (existsSync(domPath)) {\n htmlContent = await readFile(domPath, 'utf8');\n }\n if (!htmlContent) continue;\n const detected = await detectFieldsFromHtml({ tenantId, projectId, html: htmlContent });\n if (detected.length > 0) {\n const merged = await mergeIntoCatalog({ tenantId, projectId, detected });\n fieldDetectAdded += merged.added;\n }\n } catch (sErr) {\n logger.warn({ screenId: screen.id, err: sErr }, '[field-detect] skipped screen');\n }\n }\n if (fieldDetectAdded > 0 || detectLimit > 0) {\n logger.info({ screens: detectLimit, added: fieldDetectAdded, projectId }, '[field-detect] catalog populated from crawl');\n // Refresh FIELD_CATALOG skill file after catalog is populated (GAP-SF-20)\n refreshFieldCatalogSkillFile(tenantId, projectId).catch(console.error);\n }\n } catch (fdErr) {\n logger.warn({ err: fdErr }, '[field-detect] post-crawl field detection failed');\n }\n\n // Cloud storage: upload screenshots to tenant S3/R2 if configured\n const tenantStorageCfg = await getTenantStorageConfig(tenantId).catch(() => null);\n if (tenantStorageCfg) {\n let uploaded = 0;\n for (const screen of projectMap.screens as any[]) {\n for (const shot of (screen.screenshots ?? []) as any[]) {\n if (!shot.storagePath || shot.cloudUrl) continue;\n try {\n const filename = `${screen.id}-v${shot.version}.png`;\n const key = buildArtifactKey(tenantId, projectId, 'screenshots', filename, tenantStorageCfg.prefix);\n const cloudUrl = await uploadScreenshotToTenantStorage(tenantStorageCfg, shot.storagePath, key);\n await AppBrain.updateScreenshotCloudUrl(tenantId, shot.id, cloudUrl);\n uploaded++;\n } catch (uploadErr) {\n logger.warn({ screenId: screen.id, err: uploadErr }, 'Failed to upload screenshot to cloud storage');\n }\n }\n }\n if (uploaded > 0) logger.info({ uploaded, provider: tenantStorageCfg.provider }, 'Uploaded screenshots to cloud storage');\n\n // Upload DOM snapshots and markdown to tenant storage\n const { existsSync } = await import('node:fs');\n let domUploaded = 0;\n let mdUploaded = 0;\n for (const screen of projectMap.screens as any[]) {\n // DOM snapshot\n const domPath = screen.domSnapshotPath;\n if (domPath && !domPath.startsWith('https://') && existsSync(domPath)) {\n try {\n const key = buildArtifactKey(tenantId, projectId, 'dom-snapshots', `${screen.id}.json`, tenantStorageCfg.prefix);\n const cloudUrl = await uploadFileToTenantStorage(tenantStorageCfg, domPath, key, 'application/json');\n await AppBrain.updateScreenDomSnapshot(tenantId, screen.id, domPath, cloudUrl);\n domUploaded++;\n } catch (err) {\n logger.warn({ screenId: screen.id, err }, 'Failed to upload DOM snapshot to cloud storage');\n }\n }\n // Markdown extract\n const mdPath = screen.markdownPath;\n if (mdPath && !mdPath.startsWith('https://') && existsSync(mdPath)) {\n try {\n const key = buildArtifactKey(tenantId, projectId, 'markdown', `${screen.id}.md`, tenantStorageCfg.prefix);\n const cloudUrl = await uploadFileToTenantStorage(tenantStorageCfg, mdPath, key, 'text/markdown');\n await AppBrain.updateScreenMarkdownPath(tenantId, screen.id, cloudUrl);\n mdUploaded++;\n } catch (err) {\n logger.warn({ screenId: screen.id, err }, 'Failed to upload markdown to cloud storage');\n }\n }\n }\n if (domUploaded > 0) logger.info({ domUploaded, provider: tenantStorageCfg.provider }, 'Uploaded DOM snapshots to cloud storage');\n if (mdUploaded > 0) logger.info({ mdUploaded, provider: tenantStorageCfg.provider }, 'Uploaded markdown to cloud storage');\n }\n\n // AI screen grouping \u2014 clusters screens into semantic feature areas\n await groupScreensWithAI(tenantId, projectId, projectMap.screens as any, jobId, jobStartedAt);\n\n // Auto-flag stale tests: mark PENDING_REVIEW when screen elements changed since test was generated\n try {\n await withTenant(tenantId, async (tx) => {\n const staleTests = await tx.$queryRaw<{ id: string }[]>`\n SELECT tc.id\n FROM \"TestCase\" tc\n JOIN \"Screen\" s ON tc.\"screenId\" = s.id\n WHERE tc.\"tenantId\" = ${tenantId}\n AND s.\"projectId\" = ${projectId}\n AND s.\"elementHashChangedAt\" IS NOT NULL\n AND tc.\"createdAt\" IS NOT NULL\n AND s.\"elementHashChangedAt\" > tc.\"createdAt\"\n AND tc.status != 'PENDING_REVIEW'\n AND tc.status != 'RETIRED'\n `;\n if (staleTests.length > 0) {\n const ids = staleTests.map((t: { id: string }) => t.id);\n await tx.testCase.updateMany({\n where: { id: { in: ids } },\n data: { status: 'PENDING_REVIEW' },\n });\n logger.info({ count: staleTests.length, projectId }, 'Auto-flagged stale test cases as PENDING_REVIEW');\n }\n });\n } catch (staleErr) {\n logger.warn({ err: staleErr }, 'Failed to auto-flag stale tests');\n }\n\n const changed = (projectMap.screens as any[]).filter((s: any) => s.hasChange);\n if (changed.length > 0) {\n await investigateScreenChanges(tenantId, projectId, changed);\n }\n }\n } catch (skillErr) {\n logger.error({ projectId, err: skillErr }, 'Taxonomy generation failed');\n }\n });\n\n } catch (err: any) {\n clearInterval(heartbeatTimer);\n runningControllers.delete(jobId);\n const aborted = String(err).includes('CRAWL_ABORTED');\n const authFailure = !aborted && (String(err).includes('AUTH_FAILURE') || String(err).includes('SESSION_EXPIRED'));\n const checkpoint = jobCheckpoints.get(jobId);\n jobCheckpoints.delete(jobId);\n\n // Auth failed mid-crawl with work still pending \u2014 auto-requeue continuation job.\n // The crawler checkpoints the remaining queue before throwing, so checkpoint.pendingQueue\n // holds URLs not yet visited. skipUrls prevents re-crawling already-done pages.\n if (authFailure && (checkpoint?.pendingQueue?.length ?? 0) > 0) {\n const pendingAfterAuth: string[] = checkpoint!.pendingQueue!;\n logger.info({ pending: pendingAfterAuth.length }, '[crawl] auth failure mid-crawl \u2014 auto-enqueuing continuation with pending URLs');\n enqueueJob('CRAWL', {\n ...config,\n startUrls: pendingAfterAuth,\n skipUrls: [...(config.skipUrls ?? []), ...(checkpoint!.visitedUrls ?? [])],\n }).catch((enqErr: any) => logger.warn({ err: enqErr?.message }, '[crawl] auth-failure auto-segment enqueue failed (non-fatal)'));\n }\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: aborted ? 'CANCELLED' : 'FAILED',\n error: aborted ? 'Stopped by user' : String(err),\n finishedAt: new Date(),\n ...(aborted ? {} : { nextRetryAt: new Date(Date.now() + 60_000) }),\n ...(checkpoint ? { result: { checkpoint } as any } : {}),\n },\n })\n );\n\n // Notify on crawl failure (fire-and-forget)\n if (!aborted) {\n notifyAdmins(tenantId, {\n type: 'crawl.failed',\n title: 'Crawl failed',\n body: `Crawl for project failed: ${String(err)}`,\n metadata: { projectId, jobId, error: String(err) },\n }).catch(console.error);\n\n // Dispatch outbound webhooks (fire-and-forget)\n dispatchWebhook(tenantId, projectId, 'crawl.failed', {\n jobId,\n error: String(err),\n }).catch(console.error);\n }\n } finally {\n runningControllers.delete(jobId);\n }\n}\n\n/** After a job finishes, sum LLMCallLog entries for that project during the job window. */\nexport async function aggregateJobTokens(tenantId: string, jobId: string, projectId: string, startedAt: Date) {\n try {\n await withTenant(tenantId, async (tx) => {\n const rows = await tx.$queryRaw<any[]>`\n SELECT\n COALESCE(SUM(\"promptTokens\"), 0)::INT AS \"totalPromptTokens\",\n COALESCE(SUM(\"completionTokens\"), 0)::INT AS \"totalCompletionTokens\",\n COALESCE(SUM(\"cacheReadTokens\"), 0)::INT AS \"totalCacheReadTokens\",\n COALESCE(SUM(\"estimatedCostUsd\"), 0.0) AS \"totalEstimatedCostUsd\",\n COUNT(*)::INT AS \"llmCallCount\"\n FROM \"LLMCallLog\"\n WHERE \"tenantId\" = ${tenantId}\n AND \"jobId\" = ${jobId}\n `;\n const row = rows[0];\n if (!row || !row.llmCallCount) return;\n await tx.platformJob.update({\n where: { id: jobId },\n data: {\n totalPromptTokens: row.totalPromptTokens,\n totalCompletionTokens: row.totalCompletionTokens,\n totalCacheReadTokens: row.totalCacheReadTokens,\n totalEstimatedCostUsd: Number(row.totalEstimatedCostUsd),\n llmCallCount: row.llmCallCount,\n },\n });\n });\n } catch (err) {\n console.warn('[job-tokens] aggregation failed:', err);\n }\n}\n\nasync function processApiTestsJob(jobId: string, config: any) {\n const { tenantId, projectId } = config;\n const jobStartedAt = new Date();\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: jobStartedAt } })\n );\n try {\n const { generateApiTests, refreshCoverageMatrix, refreshTestCaseInventory } = await import('@detiq/agents');\n await updateJobProgress(tenantId, jobId, 20, 'Loading API contracts and endpoints\u2026');\n const result = await generateApiTests({ tenantId, projectId, apiEndpoints: config.apiEndpoints });\n await updateJobProgress(tenantId, jobId, 90, `Generated ${result.cases?.length ?? 0} API test cases`);\n refreshCoverageMatrix(tenantId, projectId).catch((e) => console.warn('[coverage-matrix]', e));\n refreshTestCaseInventory(tenantId, projectId).catch((e) => console.warn('[tc-inventory]', e));\n refreshTestCasesSkillFile(tenantId, projectId).catch(console.error);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE', progress: 100, finishedAt: new Date(),\n result: { cases: result.cases?.length ?? 0, suiteName: result.suiteName } as any,\n },\n })\n );\n aggregateJobTokens(tenantId, jobId, projectId, jobStartedAt).catch(console.error);\n } catch (err: any) {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', error: String(err), finishedAt: new Date(), nextRetryAt: new Date(Date.now() + 60_000) } })\n );\n }\n}\n\nasync function processSecurityTestsJob(jobId: string, config: any) {\n const { tenantId, projectId } = config;\n const jobStartedAt = new Date();\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: jobStartedAt } })\n );\n try {\n const { generateSecurityTests, refreshCoverageMatrix, refreshTestCaseInventory } = await import('@detiq/agents');\n await updateJobProgress(tenantId, jobId, 20, 'Mapping OWASP attack surface\u2026');\n const result = await generateSecurityTests({ tenantId, projectId, apiEndpoints: config.apiEndpoints });\n await updateJobProgress(tenantId, jobId, 90, `Generated ${result.cases?.length ?? 0} security test cases`);\n refreshCoverageMatrix(tenantId, projectId).catch((e) => console.warn('[coverage-matrix]', e));\n refreshTestCaseInventory(tenantId, projectId).catch((e) => console.warn('[tc-inventory]', e));\n refreshTestCasesSkillFile(tenantId, projectId).catch(console.error);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE', progress: 100, finishedAt: new Date(),\n result: { cases: result.cases?.length ?? 0, suiteName: result.suiteName } as any,\n },\n })\n );\n aggregateJobTokens(tenantId, jobId, projectId, jobStartedAt).catch(console.error);\n } catch (err: any) {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', error: String(err), finishedAt: new Date(), nextRetryAt: new Date(Date.now() + 60_000) } })\n );\n }\n}\n\nasync function processIntegrationTestsJob(jobId: string, config: any) {\n const { tenantId, projectId } = config;\n const jobStartedAt = new Date();\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: jobStartedAt } })\n );\n try {\n const { generateIntegrationTests, refreshCoverageMatrix, refreshTestCaseInventory } = await import('@detiq/agents');\n await updateJobProgress(tenantId, jobId, 20, 'Analysing API contracts and data models\u2026');\n const result = await generateIntegrationTests({ tenantId, projectId, apiEndpoints: config.apiEndpoints });\n await updateJobProgress(tenantId, jobId, 90, `Generated ${result.cases?.length ?? 0} integration test cases`);\n refreshCoverageMatrix(tenantId, projectId).catch((e) => console.warn('[coverage-matrix]', e));\n refreshTestCaseInventory(tenantId, projectId).catch((e) => console.warn('[tc-inventory]', e));\n refreshTestCasesSkillFile(tenantId, projectId).catch(console.error);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE', progress: 100, finishedAt: new Date(),\n result: { cases: result.cases?.length ?? 0, suiteName: result.suiteName } as any,\n },\n })\n );\n aggregateJobTokens(tenantId, jobId, projectId, jobStartedAt).catch(console.error);\n } catch (err: any) {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', error: String(err), finishedAt: new Date(), nextRetryAt: new Date(Date.now() + 60_000) } })\n );\n }\n}\n\nasync function processFigmaImportJob(jobId: string, config: any) {\n const { tenantId, projectId, fileKey } = config;\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: new Date() } })\n );\n try {\n // Prefer OAuth token (connected via Figma OAuth flow), fall back to PAT connector.\n const oauthConn = await AppBrain.getOAuthConnection(tenantId, projectId, 'FIGMA');\n let creds: figma.FigmaCredentials;\n const resolvedFileKey = fileKey ?? '';\n if (!resolvedFileKey) throw new Error('No Figma file key. Paste the full Figma file URL as the App URL.');\n\n if (oauthConn?.accessToken && oauthConn.status === 'CONNECTED') {\n // OAuth token must use Authorization: Bearer header (oauthToken field), not X-Figma-Token\n creds = { accessToken: '', oauthToken: oauthConn.accessToken, fileKey: resolvedFileKey };\n } else {\n const connector = await withTenant(tenantId, (tx) =>\n (tx as any).connector.findFirst({ where: { projectId, kind: 'FIGMA', tenantId } })\n ) as any;\n if (!connector) throw new Error('Figma connector not configured. Add your Figma token in Project Settings \u2192 Integrations.');\n const cfg = connector.config as Record<string, string>;\n const pat = cfg.accessToken_enc ? decrypt(cfg.accessToken_enc) : '';\n if (!pat) throw new Error('Figma access token missing. Edit the Figma connector and add your Personal Access Token.');\n creds = { accessToken: pat, fileKey: resolvedFileKey };\n }\n\n await updateJobProgress(tenantId, jobId, 5, 'Listing frames from Figma file\u2026');\n const frames = await figma.listScreens(creds, resolvedFileKey);\n if (frames.length === 0) {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', progress: 100, result: { screensImported: 0 } as any, finishedAt: new Date() } })\n );\n return;\n }\n\n await updateJobProgress(tenantId, jobId, 15, `Fetching images for ${frames.length} frames\u2026`);\n const nodeIds = frames.map((f: any) => f.id);\n const imageUrls = await figma.getScreenImages(creds, resolvedFileKey, nodeIds, 1);\n\n const screenshotDir = path.join(platformConfig.screenshotDir, projectId);\n await fs.mkdir(screenshotDir, { recursive: true });\n const tenantStorageCfg = await getTenantStorageConfig(tenantId).catch(() => null);\n\n let saved = 0;\n for (const frame of frames) {\n try {\n const pct = Math.round(15 + ((saved / frames.length) * 80));\n await updateJobProgress(tenantId, jobId, pct, `Saving frame ${saved + 1}/${frames.length}: ${(frame as any).name}`);\n const screen = await AppBrain.saveScreen(tenantId, projectId, (frame as any).name, `figma://file/${resolvedFileKey}/node/${(frame as any).id}`);\n const imgUrl = (imageUrls as any)[(frame as any).id];\n if (imgUrl) {\n const imgRes = await fetch(imgUrl);\n if (imgRes.ok) {\n const buf = Buffer.from(await imgRes.arrayBuffer());\n const filename = `${screen.id}-v1.png`;\n const storagePath = path.join(screenshotDir, filename);\n await fs.writeFile(storagePath, buf);\n let cloudUrl: string | undefined;\n if (tenantStorageCfg) {\n try {\n const key = buildArtifactKey(tenantId, projectId, 'screenshots', filename, tenantStorageCfg.prefix);\n cloudUrl = (await uploadScreenshotToTenantStorage(tenantStorageCfg, storagePath, key)) ?? undefined;\n } catch (e) {\n console.warn('[figma-import] cloud upload failed:', e);\n }\n }\n const w = (frame as any).boundingBox?.width ?? 1440;\n const viewport = w <= 480 ? 'MOBILE_PORTRAIT' : w <= 1024 ? 'TABLET' : 'DESKTOP';\n await AppBrain.saveScreenshot(tenantId, projectId, screen.id, storagePath, 1, viewport, cloudUrl);\n }\n }\n\n // Extract elements from Figma frame node \u2192 save for grounding + test generation\n if ((frame as any).node) {\n const figmaElements = figma.extractElements((frame as any).node);\n for (const el of figmaElements) {\n const role = el.type === 'COMPONENT' || el.type === 'COMPONENT_SET' ? 'COMPONENT'\n : el.type === 'INSTANCE' ? 'COMPONENT'\n : el.type === 'TEXT' ? 'TEXT'\n : el.type === 'FRAME' || el.type === 'GROUP' ? 'REGION'\n : el.type;\n const boundingRect = el.boundingBox\n ? { x: Math.round(el.boundingBox.x), y: Math.round(el.boundingBox.y), width: Math.round(el.boundingBox.width), height: Math.round(el.boundingBox.height) }\n : undefined;\n await AppBrain.saveElement(\n tenantId, projectId, screen.id,\n el.name, role, undefined,\n el.description ?? undefined, undefined,\n el.isInteractive,\n boundingRect, undefined, undefined,\n );\n }\n }\n\n saved++;\n } catch (err) {\n console.warn(`[figma-import] Failed to save frame:`, err);\n }\n }\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', progress: 100, result: { screensImported: saved, totalFrames: frames.length } as any, finishedAt: new Date() },\n })\n );\n\n // Post-import: run AI grouping + taxonomy skill files (same as post-crawl)\n setImmediate(async () => {\n try {\n const projectMap = await AppBrain.getProjectMap(tenantId, projectId);\n if (projectMap?.screens?.length) {\n await generateTaxonomySkillFiles(tenantId, projectId, projectMap.screens as any);\n await groupScreensWithAI(tenantId, projectId, projectMap.screens as any, jobId, new Date());\n }\n } catch (err) {\n console.error('[figma-import] post-import grouping failed:', err);\n }\n });\n\n } catch (err: any) {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', error: String(err?.message ?? err), finishedAt: new Date() } })\n );\n }\n}\n\nasync function processNativeCrawlJob(jobId: string, config: any) {\n const { tenantId, projectId } = config;\n const jobStartedAt = new Date();\n try {\n const build = await withTenant(tenantId, (tx) =>\n (tx as any).nativeAppBuild.findFirst({\n where: { tenantId, projectId },\n orderBy: { uploadedAt: 'desc' },\n })\n ) as { platform: string; fileFormat: string; s3Path: string; packageName: string | null } | null;\n\n if (!build) throw new Error('No native app build found for project');\n\n // AAB requires bundletool conversion \u2014 fail early with a clear message\n if (build.fileFormat === 'aab') {\n throw new Error(\n 'AAB format cannot be installed directly by Appium. Convert to APK using bundletool first, then re-upload the APK.'\n );\n }\n\n // Persist device targeting info on the job record\n const deviceFarm = (config.deviceFarm ?? 'local_emulator') as 'local_emulator' | 'aws_device_farm' | 'browserstack';\n await withTenant(tenantId, (tx) =>\n (tx as any).platformJob.update({\n where: { id: jobId },\n data: {\n deviceModel: config.deviceModel ?? null,\n osVersion: config.osVersion ?? null,\n deviceFarm,\n },\n })\n ).catch(() => { });\n\n const workspaceDeviceFarm = await AppBrain.getTenantDeviceFarmConfig(tenantId).catch(() => null);\n\n await runNativeCrawl({\n tenantId,\n projectId,\n jobId,\n platform: build.platform as 'android' | 'ios' | 'windows' | 'macos',\n appS3Path: build.s3Path,\n packageName: build.packageName ?? undefined,\n deviceModel: config.deviceModel,\n osVersion: config.osVersion,\n deviceFarm,\n appiumServerUrl: workspaceDeviceFarm?.appiumServerUrl,\n browserstackUsername: workspaceDeviceFarm?.browserstackUsername,\n browserstackAccessKey: workspaceDeviceFarm?.browserstackAccessKey,\n awsAccessKeyId: workspaceDeviceFarm?.awsAccessKeyId,\n awsSecretAccessKey: workspaceDeviceFarm?.awsSecretAccessKey,\n awsRegion: workspaceDeviceFarm?.awsRegion,\n awsDeviceFarmProjectArn: workspaceDeviceFarm?.awsDeviceFarmProjectArn,\n });\n\n // Post-crawl: generate taxonomy skill files + AI screen grouping (mirrors web crawl processJob)\n setImmediate(async () => {\n try {\n const projectMap = await AppBrain.getProjectMap(tenantId, projectId);\n const screens = (projectMap?.screens ?? []) as any[];\n if (screens.length > 0) {\n await generateTaxonomySkillFiles(tenantId, projectId, screens);\n await groupScreensWithAI(tenantId, projectId, screens, jobId, jobStartedAt);\n\n // Auto-flag stale tests: mark PENDING_REVIEW when screen elements changed since test was generated\n try {\n await withTenant(tenantId, async (tx) => {\n const staleTests = await tx.$queryRaw<{ id: string }[]>`\n SELECT tc.id\n FROM \"TestCase\" tc\n JOIN \"Screen\" s ON tc.\"screenId\" = s.id\n WHERE tc.\"tenantId\" = ${tenantId}\n AND s.\"projectId\" = ${projectId}\n AND s.\"elementHashChangedAt\" IS NOT NULL\n AND tc.\"createdAt\" IS NOT NULL\n AND s.\"elementHashChangedAt\" > tc.\"createdAt\"\n AND tc.status != 'PENDING_REVIEW'\n AND tc.status != 'RETIRED'\n `;\n if (staleTests.length > 0) {\n const ids = staleTests.map((t: { id: string }) => t.id);\n await tx.testCase.updateMany({\n where: { id: { in: ids } },\n data: { status: 'PENDING_REVIEW' },\n });\n logger.info({ count: staleTests.length, projectId }, 'Auto-flagged stale test cases as PENDING_REVIEW');\n }\n });\n } catch (staleErr) {\n logger.warn({ err: staleErr }, 'Failed to auto-flag stale tests');\n }\n }\n } catch (e) {\n logger.error({ err: e }, 'post-native-crawl processing failed');\n }\n });\n } catch (err: any) {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', error: String(err?.message ?? err), finishedAt: new Date() } })\n ).catch(() => { });\n }\n}\n\nasync function processGenerateTestsFromReqsJob(jobId: string, config: any) {\n const { tenantId, projectId, reqIds, suiteId, suiteName } = config;\n try {\n await updateJobProgress(tenantId, jobId, 5, 'Loading requirements');\n\n const { askTracked, deterministicSeed, getTaskAgentLLMConfig } = await import('@detiq/agents');\n const llmCfg = await getTaskAgentLLMConfig(tenantId, 'testGeneration');\n\n const all = await AppBrain.listRequirements(tenantId, projectId, {}) as any[];\n const reqs = reqIds?.length\n ? all.filter((r: any) => reqIds.includes(r.reqId))\n : all.filter((r: any) => r.status === 'unbuilt' || r.status === 'pending');\n\n if (reqs.length === 0) {\n await updateJobProgress(tenantId, jobId, 100, 'No pending requirements found');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', finishedAt: new Date(), result: { created: 0, skipped: 0 } as any } })\n );\n return;\n }\n\n // Resolve or create suite\n let resolvedSuiteId = suiteId;\n if (!resolvedSuiteId) {\n const name = suiteName?.trim() || 'Requirements Tests';\n const suite = await withTenant(tenantId, async (tx) => {\n const existing = await (tx as any).testSuite.findFirst({ where: { tenantId, projectId, name } });\n if (existing) return existing;\n return (tx as any).testSuite.create({ data: { tenantId, projectId, name } });\n }) as any;\n resolvedSuiteId = suite.id;\n }\n\n await updateJobProgress(tenantId, jobId, 20, `Generating tests for ${reqs.length} requirements`);\n\n const reqBlock = reqs.map((r: any) => [\n `## ${r.reqId}: ${r.title}`,\n `Priority: ${r.priority} | Type: ${r.type}`,\n r.acceptanceCriteria?.length ? `Acceptance Criteria:\\n${r.acceptanceCriteria.map((c: string) => `- ${c}`).join('\\n')}` : '',\n r.businessRules?.length ? `Business Rules:\\n${r.businessRules.map((b: string) => `- ${b}`).join('\\n')}` : '',\n ].filter(Boolean).join('\\n')).join('\\n\\n');\n\n const system = `You are ZeTa-AI's test case generator. Convert requirements into structured test cases.\nReturn ONLY valid JSON: { \"cases\": [ { \"title\": string, \"kind\": string, \"priority\": string, \"preconditions\": string, \"steps\": [{\"action\": string, \"data\": string, \"expected\": string}], \"expectedResult\": string, \"tags\": string[], \"reqRef\": string } ] }\nKind must be one of: POSITIVE, NEGATIVE, EDGE, SECURITY, ACCESSIBILITY, INTEGRATION, E2E, SMOKE, REGRESSION.\nPriority must be one of: CRITICAL, HIGH, MEDIUM, LOW.\nGenerate 1-3 test cases per requirement (positive + negative + edge if applicable).\nEach test case must reference its reqRef (the requirement ID like REQ-001).`;\n\n const user = `Generate test cases for these requirements:\\n\\n${reqBlock}`;\n\n let parsed: { cases: any[] } = { cases: [] };\n try {\n const reqSeed = deterministicSeed(`req-cases:${reqs.map((r: any) => r.reqId).sort().join('|').slice(0, 300)}`);\n const { text, usage } = await askTracked(system, user, llmCfg, undefined, undefined, { temperature: 0, seed: reqSeed });\n if (usage) recordLLMCall({ tenantId, projectId, jobId, agentType: 'generate_tests_from_reqs', label: 'test_case_generation', usage }).catch(() => { });\n const json = text.match(/\\{[\\s\\S]*\\}/)?.[0];\n if (json) parsed = JSON.parse(json);\n } catch (e: any) {\n throw new Error('AI failed to generate test cases: ' + (e.message ?? 'unknown'));\n }\n\n await updateJobProgress(tenantId, jobId, 70, `Saving ${parsed.cases?.length ?? 0} test cases`);\n\n const VALID_KINDS = new Set(['POSITIVE', 'NEGATIVE', 'EDGE', 'STATE_TRANSITION', 'ACCESSIBILITY', 'SECURITY', 'INTEGRATION', 'E2E', 'API', 'EXPLORATORY', 'UAT', 'PERFORMANCE', 'COMPATIBILITY', 'L10N', 'USABILITY', 'LOAD', 'UNIT', 'MUTATION', 'AB_TEST', 'ALPHA_BETA', 'SYSTEM', 'SMOKE', 'REGRESSION', 'SANITY']);\n const VALID_PRIORITIES = new Set(['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']);\n const normalized = (parsed.cases ?? []).map((c: any) => ({\n ref: c.reqRef ?? null,\n traceTo: c.reqRef ?? null,\n title: c.title || 'Untitled',\n kind: VALID_KINDS.has((c.kind ?? '').toUpperCase()) ? c.kind.toUpperCase() : 'POSITIVE',\n priority: VALID_PRIORITIES.has((c.priority ?? '').toUpperCase()) ? c.priority.toUpperCase() : 'MEDIUM',\n preconditions: c.preconditions ?? '',\n steps: Array.isArray(c.steps) ? c.steps : [],\n expectedResult: c.expectedResult ?? '',\n tags: Array.isArray(c.tags) ? c.tags : [],\n }));\n\n const { created, skipped } = await AppBrain.importTestCasesFromArtifact(\n tenantId, projectId, resolvedSuiteId, normalized, { duplicateMode: 'skip' }\n );\n\n // Mark requirements as covered\n const coveredReqIds = new Set(normalized.map((c: any) => c.ref).filter(Boolean));\n await Promise.all(\n reqs\n .filter((r: any) => coveredReqIds.has(r.reqId))\n .map((r: any) => AppBrain.updateRequirement(tenantId, projectId, r.reqId, { status: 'covered' }).catch(() => { }))\n );\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', finishedAt: new Date(), result: { created: created.length, skipped, suiteId: resolvedSuiteId } as any, progress: 100 },\n })\n );\n } catch (e: any) {\n const msg = e?.message ?? 'Unknown error';\n logger.error({ jobId, err: msg }, 'processGenerateTestsFromReqsJob failed');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', finishedAt: new Date(), error: msg } })\n ).catch(() => { });\n }\n}\n\n/**\n * Runs every APPROVED test case in a project (optionally scoped to one suite)\n * against the agent-free Playwright executor (test-executor.ts) and aggregates\n * the results into the TestRun row created synchronously by POST /run-tests\n * (see ci.ts) \u2014 this is what makes the CI gate (GET /test-results/latest)\n * finally reflect what CI itself triggered, instead of a stale or nonexistent\n * result from an unrelated, manually-triggered, agent-connected suite run.\n */\nasync function processRunTestSuiteJob(jobId: string, config: any) {\n const { tenantId, projectId, testRunId, suiteId, storageState, browser, shardIndex, totalShards } = config as any;\n const runStart = Date.now();\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: new Date() } })\n );\n\n try {\n let testCases = await AppBrain.listTestCases(tenantId, projectId, {\n status: 'APPROVED',\n ...(suiteId ? { suiteId } : {}),\n }) as Array<{ id: string }>;\n\n // Shard filtering: when running a distributed matrix (CI-triggered multi-browser/shard),\n // each worker only executes its own slice \u2014 testCases[shardIndex::totalShards].\n if (typeof totalShards === 'number' && totalShards > 1 && typeof shardIndex === 'number') {\n testCases = testCases.filter((_, i) => i % totalShards === shardIndex);\n }\n\n if (testCases.length === 0) {\n // Nothing approved to run isn't a CI failure \u2014 same as any test runner\n // reporting \"0 tests, 0 failures\" when a suite is empty.\n await AppBrain.completeTestRun(tenantId, testRunId, { total: 0, passed: 0, failed: 0, skipped: 0, duration: 0 });\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', progress: 100, finishedAt: new Date(), result: { total: 0 } as any },\n })\n );\n return;\n }\n\n const aiHealingEnabled = await isFeatureEnabled(tenantId, 'AI_SELF_HEALING');\n const SCREENSHOT_DIR = process.env.SCREENSHOT_DIR ?? '/tmp/zeta-screenshots';\n const screenshotDir = config.screenshotDir || platformConfig.screenshotDir || SCREENSHOT_DIR;\n const VALID_BROWSERS = ['chromium', 'firefox', 'webkit'];\n const validBrowser = VALID_BROWSERS.includes(browser) ? browser : 'chromium';\n\n let passed = 0;\n let failed = 0;\n const runResults: Array<{ testCaseId: string; status: string }> = [];\n for (let i = 0; i < testCases.length; i += TEST_EXEC_CONCURRENCY) {\n const batch = testCases.slice(i, i + TEST_EXEC_CONCURRENCY);\n await Promise.all(batch.map(async (tc) => {\n try {\n const full = await AppBrain.getTestCaseForExecution(tenantId, tc.id) as any;\n const baseUrl = full?.screen?.project?.appUrl ?? '';\n const execProjectId = full?.screen?.project?.id ?? projectId;\n const dataRows: Array<{ id: string; rowIndex: number; data: Record<string, string> }> =\n full?.dataSet?.rows ?? [];\n\n // Data-driven: run once per row (substituting placeholders). If no data set, run once with no substitution.\n const runs = dataRows.length > 0 ? dataRows : [null];\n\n for (const row of runs) {\n let tcForPlan = full ?? tc;\n if (row !== null) {\n // Clone the test case with interpolated steps and title\n const rawSteps = Array.isArray(tcForPlan.steps) ? tcForPlan.steps as string[] : [];\n tcForPlan = {\n ...tcForPlan,\n steps: rawSteps.map((s: string) => interpolatePlaceholders(s, row.data)),\n title: interpolatePlaceholders(tcForPlan.title ?? '', row.data),\n preconditions: tcForPlan.preconditions\n ? interpolatePlaceholders(tcForPlan.preconditions, row.data)\n : tcForPlan.preconditions,\n };\n }\n\n const plan = await generateExecutionPlan(tenantId, tcForPlan, baseUrl);\n const result = await executeTestPlan(plan, {\n screenshotDir,\n testRunId,\n maxRuns: 1,\n browser: validBrowser as 'chromium' | 'firefox' | 'webkit',\n storageState,\n healCtx: aiHealingEnabled ? { tenantId, projectId: execProjectId } : undefined,\n });\n const status = result.status === 'PASSED' ? 'PASSED' : result.status === 'ERROR' ? 'ERROR' : 'FAILED';\n if (status === 'PASSED') passed++; else failed++;\n runResults.push({ testCaseId: tc.id, status });\n await AppBrain.saveTestResult(tenantId, {\n runId: testRunId,\n testCaseId: tc.id,\n status,\n duration: result.totalDuration,\n ...(result.rootCauseSummary\n ? { failureAnalysis: result.rootCauseSummary, failureAnalysisAt: new Date() }\n : {}),\n ...(row !== null ? { dataRowId: row.id, dataRowIndex: row.rowIndex } : {}),\n ...((result.consoleLogs?.length || result.networkErrors?.length) ? {\n logs: {\n consoleLogs: result.consoleLogs ?? [],\n networkErrors: result.networkErrors ?? [],\n },\n } : {}),\n });\n }\n await AppBrain.recordFlakinessOutcome(tenantId, tc.id).catch(() => { });\n } catch (err: any) {\n failed++;\n runResults.push({ testCaseId: tc.id, status: 'ERROR' });\n await AppBrain.saveTestResult(tenantId, {\n runId: testRunId,\n testCaseId: tc.id,\n status: 'ERROR',\n error: String(err?.message ?? err).slice(0, 500),\n }).catch(() => { });\n }\n }));\n await updateJobProgress(\n tenantId, jobId,\n Math.round((Math.min(i + TEST_EXEC_CONCURRENCY, testCases.length) / testCases.length) * 100),\n `Ran ${Math.min(i + TEST_EXEC_CONCURRENCY, testCases.length)}/${testCases.length} test cases`,\n );\n }\n\n await AppBrain.completeTestRun(tenantId, testRunId, {\n total: testCases.length,\n passed,\n failed,\n skipped: 0,\n duration: Date.now() - runStart,\n });\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', progress: 100, finishedAt: new Date(), result: { total: testCases.length, passed, failed } as any },\n })\n );\n\n // Same full fan-out as the interactive web-driven run path (in-app,\n // generic webhook, suite-owner failure emails, GitHub check-run, Slack,\n // Teams, alert emails on failure) \u2014 a CI-triggered run has nobody\n // watching the SSE stream, so this is the only place these fire from.\n notifyRunCompletion(\n tenantId, projectId, testRunId,\n failed === 0 ? 'COMPLETED' : 'FAILED',\n { passed, failed, total: testCases.length },\n runResults,\n ).catch(console.error);\n refreshTestExecutionHistorySkillFile(tenantId, projectId).catch(console.error);\n } catch (err: any) {\n const msg = String(err?.message ?? err).slice(0, 500);\n logger.error({ jobId, testRunId, err: msg }, 'processRunTestSuiteJob failed');\n await AppBrain.failTestRun(tenantId, testRunId, msg).catch(() => { });\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', finishedAt: new Date(), error: msg } })\n ).catch(() => { });\n }\n}\n\nasync function processGenerateSkillFilesJob(jobId: string, config: any) {\n const { tenantId, projectId } = config;\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: new Date() } })\n );\n try {\n await updateJobProgress(tenantId, jobId, 10, 'Loading project data\u2026');\n const projectMap = await AppBrain.getProjectMap(tenantId, projectId);\n if (!projectMap) throw new Error('Project not found.');\n const screens = (projectMap.screens ?? []) as any[];\n // Manual regeneration has no fresh crawl result \u2014 reuse the metrics and\n // endpoints persisted on the last completed crawl job so the Performance\n // Baseline and API Contract aren't wiped to \"no data captured\".\n const lastCrawl = await withTenant(tenantId, (tx) =>\n tx.platformJob.findFirst({\n where: { projectId, type: 'CRAWL', status: 'DONE' },\n orderBy: { finishedAt: 'desc' },\n select: { result: true },\n })\n );\n const lastResult = ((lastCrawl as any)?.result ?? {}) as any;\n await updateJobProgress(tenantId, jobId, 25, `Generating ${screens.length} app intelligence skill files\u2026`);\n await generateTaxonomySkillFiles(tenantId, projectId, screens, lastResult.apiEndpoints, lastResult.performanceMetrics);\n await updateJobProgress(tenantId, jobId, 85, 'Refreshing testing intelligence skill files\u2026');\n await Promise.allSettled([\n refreshTestCasesSkillFile(tenantId, projectId),\n refreshTestDataSkillFile(tenantId, projectId),\n refreshTestScriptSkillFile(tenantId, projectId),\n refreshRequirementsCoverageSkillFile(tenantId, projectId),\n refreshTestExecutionHistorySkillFile(tenantId, projectId),\n refreshVisualRegressionHistorySkillFile(tenantId, projectId),\n refreshLocatorStrategyWithHealingData(tenantId, projectId),\n refreshFieldCatalogSkillFile(tenantId, projectId),\n ]);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', progress: 100, finishedAt: new Date() } })\n );\n } catch (err: any) {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', error: String(err?.message ?? err), finishedAt: new Date() } })\n );\n }\n}\n\n// DATA_RETENTION_PURGE \u2014 deletes AuditLog rows older than each tenant's\n// dataRetentionDays setting (default 365), and LLMCallLog rows older than 90\n// days (fixed, no per-tenant override). Runs once daily via the cron in\n// worker.ts. The job has no tenantId/projectId \u2014 it sweeps all tenants so\n// enqueueJob() is not used; the worker.ts cron calls this directly.\nexport async function processDataRetentionPurge(): Promise<void> {\n logger.info('[data-retention] starting purge run');\n\n // LLMCallLog: fixed 90-day retention across all tenants (uses calledAt, not createdAt)\n const llmCutoff = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);\n const llmResult = await prismaAdmin.lLMCallLog.deleteMany({\n where: { calledAt: { lt: llmCutoff } },\n });\n logger.info({ deleted: llmResult.count }, '[data-retention] LLMCallLog purged');\n\n // AuditLog: per-tenant retention via TenantSetting.dataRetentionDays\n const tenants = await prismaAdmin.tenant.findMany({ select: { id: true } });\n let auditTotal = 0;\n\n for (const { id: tenantId } of tenants) {\n // Find the most restrictive (lowest) dataRetentionDays across this tenant's\n // settings rows, falling back to 365 if no setting row exists.\n const setting = await prismaAdmin.tenantSetting.findFirst({\n where: { tenantId },\n orderBy: { dataRetentionDays: 'asc' },\n select: { dataRetentionDays: true },\n });\n const retentionDays = setting?.dataRetentionDays ?? (parseInt(process.env.DEFAULT_DATA_RETENTION_DAYS ?? '') || 365);\n const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);\n\n const result = await prismaAdmin.auditLog.deleteMany({\n where: { tenantId, createdAt: { lt: cutoff } },\n });\n auditTotal += result.count;\n }\n\n logger.info({ deleted: auditTotal, tenants: tenants.length }, '[data-retention] AuditLog purged');\n\n // AuthSession: fixed 12-month retention \u2014 IP addresses are PII under GDPR.\n // Only purge revoked sessions; active (non-revoked) sessions are kept.\n const SESSION_RETENTION_DAYS = 365;\n const sessionCutoff = new Date(Date.now() - SESSION_RETENTION_DAYS * 24 * 60 * 60_000);\n const sessionResult = await prismaAdmin.authSession.deleteMany({\n where: {\n createdAt: { lt: sessionCutoff },\n revokedAt: { not: null },\n },\n });\n logger.info({ deleted: sessionResult.count }, '[data-retention] AuthSession purged');\n\n // PlatformJob: delete completed jobs older than 90 days (GAP-24)\n const ninetyDaysAgo = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);\n const deletedJobs = await prismaAdmin.platformJob.deleteMany({\n where: {\n status: { in: ['DONE', 'FAILED'] },\n dlq: false,\n inline: false,\n finishedAt: { lt: ninetyDaysAgo },\n },\n });\n if (deletedJobs.count > 0) {\n logger.info({ count: deletedJobs.count }, '[data-retention] deleted old completed jobs');\n }\n}\n\n// \u2500\u2500 URL Discovery Job \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Three-phase URL discovery: sitemap seed \u2192 unauthenticated BFS spider \u2192 authenticated BFS\n// spider (when crawl credentials exist). Persists discovered URLs to crawlConfig and marks the\n// job DONE with the full URL list so the UI can poll for results.\n\nasync function processDiscoverJob(jobId: string, config: any, opts?: ProcessJobOpts) {\n const { tenantId, projectId } = config;\n\n const controller = new AbortController();\n runningControllers.set(jobId, controller);\n\n // Independent heartbeat timer \u2014 keeps heartbeatAt fresh even when no URLs are\n // arriving (e.g. during auth login, page-load waits, 2FA handling).\n const heartbeatTimer = setInterval(() => {\n void heartbeatJob(tenantId, jobId);\n }, JOB_HEARTBEAT_INTERVAL_MS);\n\n // Shared accumulator \u2014 updated by onUrlsDiscovered callback as each page is visited.\n // This lets the frontend poll and see a growing list before the job finishes.\n const allUrls = new Set<string>();\n let lastPartialFlush = 0;\n let lastFlushedPct = 0; // high-water mark \u2014 progress never decreases\n\n const flushPartialUrls = async (pct: number, step: string) => {\n const now = Date.now();\n // Flush to DB at most every 5 seconds to avoid excessive writes\n if (now - lastPartialFlush < 5_000 && allUrls.size < 5) return;\n const monotonic = Math.max(pct, lastFlushedPct);\n lastFlushedPct = monotonic;\n lastPartialFlush = now;\n try {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { progress: monotonic, result: { step, partialUrls: Array.from(allUrls) } as any, heartbeatAt: new Date() },\n })\n );\n } catch { /* non-fatal */ }\n await opts?.onProgress?.(monotonic);\n };\n\n const onProgress = async (pct: number, step: string) => {\n await updateJobProgress(tenantId, jobId, pct, step);\n await opts?.onProgress?.(pct);\n };\n\n try {\n // Mark RUNNING inside the try block so any DB error is caught by the internal\n // handler below (which writes result.error). If this were before the try block,\n // a DB failure would bubble up to dispatchClaimedJob's catch, which writes to\n // the `error` column instead of `result.error` \u2014 the frontend would then show\n // \"Unknown error\" rather than the actual message.\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: new Date() } })\n );\n // Load decrypted project crawl config for credentials/proxy/sitemap settings.\n // Reading Project.crawlConfig directly leaves encrypted fields as *_enc, so\n // discovery would see username but miss password/storageState/authToken.\n const crawlCfg = await AppBrain.getCrawlConfig(tenantId, projectId).catch(() => null) ?? {};\n const appUrl: string = config.appUrl ?? crawlCfg.appUrl;\n if (!appUrl) throw new Error('No app URL configured. Save the crawl settings first.');\n\n const { getSystemConfig } = await import('@detiq/core');\n const crawlTuning = await getSystemConfig('crawl_tuning');\n const maxDiscoverUrls: number = config.maxDiscoverUrls ?? parseInt(String(crawlTuning?.maxDiscoverUrls ?? '2000'), 10);\n const proxyUrl: string | undefined = config.proxyUrl ?? crawlCfg.proxyUrl;\n const screenshotDir = os.tmpdir();\n const discoverStorageConfig = await getTenantStorageConfig(tenantId).catch(() => null);\n\n await onProgress(3, 'Seeding from sitemap/robots.txt\u2026');\n\n // Phase 1 \u2014 sitemap + robots.txt seed (fast, no browser)\n const sitemapResult = await discoverProjectUrls(appUrl, {\n sitemapUrl: config.sitemapUrl ?? crawlCfg.sitemapUrl,\n proxyUrl,\n }).catch(() => ({ candidates: [] as Array<{ url: string; source: string }>, sitemapFound: false }));\n const sitemapUrls = sitemapResult.candidates.map((c: any) => c.url as string);\n // Seed accumulator with sitemap URLs so they show immediately\n for (const u of sitemapUrls) allUrls.add(u);\n logger.info({ count: sitemapUrls.length, sitemapFound: sitemapResult.sitemapFound }, '[discover] Phase 1 sitemap done');\n\n // Phase 2 \u2014 unauthenticated BFS spider\n await onProgress(8, `Unauthenticated spider from ${appUrl}\u2026`);\n const unauthResult = await crawlProject({\n tenantId,\n projectId,\n appUrl,\n discoveryMode: true,\n startUrls: sitemapUrls.length > 0 ? sitemapUrls : undefined,\n screenshotDir,\n maxScreens: maxDiscoverUrls,\n proxyUrl,\n useFirefox: crawlCfg.useFirefox,\n storageConfig: discoverStorageConfig,\n abortSignal: controller.signal,\n jobId,\n onUrlsDiscovered: async (urls: string[]) => {\n for (const u of urls) allUrls.add(u);\n const pct = 8 + Math.round((allUrls.size / Math.max(1, maxDiscoverUrls)) * 42);\n await flushPartialUrls(Math.min(pct, 49), `Discovered ${allUrls.size} URLs\u2026`);\n },\n }, async (pct: number, step: string) => {\n await flushPartialUrls(8 + Math.round(pct * 0.42), `Unauthenticated: ${step}`);\n }).catch((err: any) => {\n if (err?.message === 'CRAWL_ABORTED') throw err;\n logger.warn({ err: err?.message }, '[discover] Phase 2 unauthenticated spider failed \u2014 continuing with partial results');\n return { discoveredUrls: [] as string[] };\n });\n\n for (const u of ((unauthResult as any).discoveredUrls ?? [] as string[])) allUrls.add(u);\n logger.info({ count: allUrls.size }, '[discover] Phase 2 unauth spider done');\n\n // Phase 3 \u2014 authenticated BFS spider (only when credentials configured)\n // getCrawlConfig returns flat fields (username/password/totpSecret) \u2014 NOT nested under .credentials\n let resolvedSso: {\n ssoProvider?: string;\n ssoDomain?: string;\n ssoClientId?: string;\n ssoClientSecret?: string;\n ssoScope?: string;\n } = {};\n if (crawlCfg.ssoProviderId) {\n const ssoProviders = await AppBrain.getWorkspaceSsoProviders(tenantId).catch(() => []);\n const ssoProvider = ssoProviders.find((p: any) => p.id === crawlCfg.ssoProviderId);\n if (ssoProvider) {\n resolvedSso = {\n ssoProvider: ssoProvider.provider,\n ssoDomain: ssoProvider.domain,\n ssoClientId: ssoProvider.clientId,\n ssoClientSecret: ssoProvider.clientSecret,\n ssoScope: ssoProvider.scope,\n };\n }\n }\n\n const hasCreds = !!(\n crawlCfg.username ||\n crawlCfg.storageState ||\n crawlCfg.authToken ||\n crawlCfg.chromeProfilePath ||\n (crawlCfg.httpBasicUsername && crawlCfg.httpBasicPassword)\n );\n if (hasCreds && !controller.signal.aborted) {\n await onProgress(52, 'Authenticated spider\u2026');\n const runtimeCreds = crawlCfg.username\n ? {\n username: crawlCfg.username,\n password: crawlCfg.password ?? '',\n ...(crawlCfg.totpSecret\n ? { getOneTimeCode: async () => generateTOTP(crawlCfg.totpSecret!) }\n : {}),\n }\n : undefined;\n\n const PHASE3_TIMEOUT_MS = 20 * 60_000; // 20-minute cap on authenticated spider\n const authResult = await Promise.race([\n crawlProject({\n tenantId,\n projectId,\n appUrl,\n discoveryMode: true,\n credentials: runtimeCreds,\n storageState: crawlCfg.storageState,\n httpBasicUsername: crawlCfg.httpBasicUsername,\n httpBasicPassword: crawlCfg.httpBasicPassword,\n authToken: crawlCfg.authToken,\n authTokenHeader: crawlCfg.authTokenHeader,\n authTokenPrefix: crawlCfg.authTokenPrefix,\n startUrls: crawlCfg.postLoginUrl\n ? [crawlCfg.postLoginUrl, ...(sitemapUrls.length > 0 ? sitemapUrls : [])]\n : (sitemapUrls.length > 0 ? sitemapUrls : undefined),\n screenshotDir,\n maxScreens: maxDiscoverUrls,\n proxyUrl,\n chromeProfilePath: crawlCfg.chromeProfilePath,\n useFirefox: crawlCfg.useFirefox,\n storageConfig: discoverStorageConfig,\n captchaSolverApiKey: crawlCfg.captchaSolverApiKey,\n captchaSolverProvider: crawlCfg.captchaSolverProvider,\n mailslurpApiKey: crawlCfg.mailslurpApiKey,\n mailslurpInboxId: crawlCfg.mailslurpInboxId,\n loginInstructions: crawlCfg.loginInstructions,\n ...resolvedSso,\n abortSignal: controller.signal,\n jobId,\n onUrlsDiscovered: async (urls: string[]) => {\n for (const u of urls) allUrls.add(u);\n const pct = 52 + Math.round((allUrls.size / Math.max(1, maxDiscoverUrls)) * 38);\n await flushPartialUrls(Math.min(pct, 89), `Discovered ${allUrls.size} URLs (authenticated)\u2026`);\n },\n }, async (pct: number, step: string) => {\n await flushPartialUrls(52 + Math.round(pct * 0.38), `Authenticated: ${step}`);\n }),\n new Promise<never>((_, rej) => setTimeout(() => rej(new Error('AUTH_PHASE_TIMEOUT')), PHASE3_TIMEOUT_MS)),\n ]).catch((err: any) => {\n if (err?.message === 'CRAWL_ABORTED') throw err;\n const reason = err?.message === 'AUTH_PHASE_TIMEOUT'\n ? 'Authenticated spider timed out after 20 minutes'\n : err?.message;\n logger.warn({ reason }, '[discover] Phase 3 authenticated spider failed \u2014 using unauthenticated results');\n return { discoveredUrls: [] as string[] };\n });\n\n for (const u of (((authResult as any).discoveredUrls) ?? [] as string[])) {\n allUrls.add(u);\n }\n logger.info({ count: allUrls.size }, '[discover] Phase 3 auth spider done');\n }\n\n // Persist discovered URLs to project crawl config\n // Filter sentinel strings (_pending_, _auth_wall_, etc.) that may have leaked in\n // via onUrlsDiscovered callbacks fired before visited-map entries were updated.\n const discoveredUrls = Array.from(allUrls).filter(u => u.startsWith('http'));\n const existingCfg = await AppBrain.getCrawlConfig(tenantId, projectId).catch(() => ({}));\n await AppBrain.saveCrawlConfig(tenantId, projectId, {\n ...(existingCfg as any),\n discoveredUrls,\n discoveredAt: new Date().toISOString(),\n });\n\n await onProgress(100, `Done \u2014 ${discoveredUrls.length} URLs discovered`);\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE',\n result: {\n discoveredUrls,\n count: discoveredUrls.length,\n sitemapFound: sitemapResult.sitemapFound,\n } as any,\n progress: 100,\n finishedAt: new Date(),\n },\n })\n );\n\n notifyAdmins(tenantId, {\n type: 'discovery.done',\n title: 'URL Discovery completed',\n body: `Found ${discoveredUrls.length} URLs for the project.`,\n metadata: { projectId, jobId },\n }).catch(console.error);\n\n } catch (err: any) {\n logger.error({ err: err?.message, jobId }, '[discover] Job failed');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: err?.message === 'CRAWL_ABORTED' ? 'CANCELLED' : 'FAILED',\n result: { error: err?.message ?? String(err) } as any,\n finishedAt: new Date(),\n },\n })\n ).catch(console.error);\n } finally {\n clearInterval(heartbeatTimer);\n runningControllers.delete(jobId);\n }\n}\n\n// \u2500\u2500 Site Monitor Job \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Lightweight scheduled content check: fetches configured URLs, diffs content\n// hashes against stored baselines, fires content.changed webhooks on drift.\n// Baselines are stored in project.crawlConfig.monitorBaselines (url\u2192hash map).\n\nasync function processMonitorJob(jobId: string, config: any, opts?: ProcessJobOpts) {\n const { tenantId, projectId } = config;\n const controller = new AbortController();\n runningControllers.set(jobId, controller);\n\n try {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: new Date() } })\n );\n\n const proj: any = await withTenant(tenantId, (tx) =>\n (tx as any).project.findUnique({ where: { id: projectId }, select: { crawlConfig: true } })\n );\n const crawlCfg = proj?.crawlConfig ?? {};\n const monitorUrls: string[] = config.monitorUrls ?? crawlCfg.monitorUrls ?? [];\n\n if (monitorUrls.length === 0) {\n throw new Error('No monitor URLs configured. Add monitorUrls to crawl config or job config.');\n }\n\n await updateJobProgress(tenantId, jobId, 10, `Monitoring ${monitorUrls.length} URL(s)...`);\n\n // Baseline: stored baselines from previous monitor run take priority;\n // project screens' elementHash is the fallback for first-run.\n const storedBaselines: Record<string, string> = crawlCfg.monitorBaselines ?? {};\n const projectScreens: Array<{ url: string | null; elementHash: string | null }> =\n await withTenant(tenantId, (tx) =>\n tx.screen.findMany({ where: { projectId }, select: { url: true, elementHash: true } })\n ).catch(() => []) as any;\n const screenHashMap: Record<string, string> = {};\n for (const s of projectScreens) {\n if (s.url && s.elementHash) screenHashMap[s.url] = s.elementHash;\n }\n\n const results: Array<{\n url: string; changed: boolean; reason: string;\n currentHash: string; previousHash: string | null;\n }> = [];\n const newBaselines: Record<string, string> = { ...storedBaselines };\n\n for (let i = 0; i < monitorUrls.length; i++) {\n if (controller.signal.aborted) break;\n const url = monitorUrls[i];\n const pct = 10 + Math.floor((i / monitorUrls.length) * 80);\n await updateJobProgress(tenantId, jobId, pct, `Checking ${url}...`);\n opts?.onProgress?.(pct);\n\n try {\n const crawlResult = await httpCrawl({ appUrl: url, maxScreens: 1, respectRobotsTxt: false, timeoutMs: parseInt(process.env.CRAWL_TIMEOUT_MS ?? '20000', 10) });\n const screen = crawlResult.screens[0];\n if (!screen) {\n results.push({ url, changed: false, reason: 'fetch_empty', currentHash: '', previousHash: null });\n continue;\n }\n const currentHash = hashContent(normalizeText(screen.markdown));\n const previousHash = storedBaselines[url] ?? screenHashMap[url] ?? null;\n newBaselines[url] = currentHash;\n\n if (!previousHash) {\n results.push({ url, changed: false, reason: 'new_baseline', currentHash, previousHash: null });\n } else if (previousHash === currentHash) {\n results.push({ url, changed: false, reason: 'unchanged', currentHash, previousHash });\n } else {\n results.push({ url, changed: true, reason: 'content_changed', currentHash, previousHash });\n }\n } catch (fetchErr: any) {\n results.push({ url, changed: false, reason: `error:${fetchErr?.message ?? String(fetchErr)}`, currentHash: '', previousHash: null });\n }\n }\n\n // Persist updated baselines so next run has fresh reference points.\n await withTenant(tenantId, (tx) =>\n (tx as any).project.update({\n where: { id: projectId },\n data: { crawlConfig: { ...crawlCfg, monitorBaselines: newBaselines } as any },\n })\n ).catch((e: any) => logger.warn({ err: e }, '[monitor] baseline persist failed'));\n\n const changedUrls = results.filter(r => r.changed);\n\n if (changedUrls.length > 0) {\n dispatchWebhook(tenantId, projectId, 'content.changed', {\n jobId,\n changedUrls: changedUrls.map(r => ({ url: r.url, previousHash: r.previousHash, currentHash: r.currentHash })),\n totalChecked: results.length,\n changedCount: changedUrls.length,\n }).catch(console.error);\n }\n\n await updateJobProgress(tenantId, jobId, 100, 'Monitor check complete');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE',\n progress: 100,\n finishedAt: new Date(),\n result: { results, changedCount: changedUrls.length, totalChecked: results.length } as any,\n },\n })\n );\n } catch (err: any) {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'FAILED', error: String(err?.message ?? err), finishedAt: new Date() },\n })\n );\n } finally {\n runningControllers.delete(jobId);\n }\n}\n\n// \u2500\u2500\u2500 IMPORT_TESTS job processor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Async counterpart to the synchronous POST /artifacts/:id/import-tests route.\n// Enqueued by POST /modernization/bulk-import for each artifact in a batch.\n// Config: { tenantId, projectId, artifactId, suiteId?, autoLabel?, sessionId? }\n\nasync function processImportTestsJob(jobId: string, config: any) {\n const { tenantId, projectId, artifactId, autoLabel, sourcePlatform } = config;\n const jobStart = new Date();\n let suiteId: string = config.suiteId ?? 'new';\n\n await updateJobProgress(tenantId, jobId, 5, 'Loading artifact');\n\n const artifact = await withTenant(tenantId, (tx) =>\n (tx as any).projectArtifact.findUnique({ where: { id: artifactId } })\n );\n if (!artifact || (artifact as any).tenantId !== tenantId) {\n throw new Error(`Artifact ${artifactId} not found`);\n }\n\n const content: string = (artifact as any).content ?? '';\n const filename: string = (artifact as any).originalName ?? (artifact as any).name ?? '';\n const kind: string = (artifact as any).kind ?? '';\n\n // Detect format using same logic as the synchronous route\n function detectFormat(fn: string, k: string, c: string): string {\n if (fn.endsWith('.feature')) return 'GHERKIN';\n if (fn.match(/\\.(xlsx|xls|csv)$/i) || k === 'SPREADSHEET') return 'EXCEL';\n if (fn.endsWith('.txt') && c && c.split('\\n').some((l) => l.trim().startsWith('|'))) return 'EXCEL';\n return 'AI_EXTRACT';\n }\n const format = detectFormat(filename, kind, content);\n\n await updateJobProgress(tenantId, jobId, 15, `Parsing ${filename}`);\n\n const { parseTestCases, labelTestCasesBatch } = await import('@detiq/agents');\n let res = await parseTestCases(content, format, tenantId, (artifact as any).columnMapping ?? undefined, undefined, undefined, sourcePlatform);\n if (res.cases.length === 0 && format !== 'AI_EXTRACT') {\n await updateJobProgress(tenantId, jobId, 25, 'Retrying with AI extraction');\n try {\n res = await parseTestCases(content, 'AI_EXTRACT', tenantId, undefined, undefined, undefined, sourcePlatform);\n } catch {\n // AI unavailable (no LLM configured) \u2014 proceed with 0 cases\n }\n }\n\n const parsedCases = res.cases;\n if (parsedCases.length === 0) {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', progress: 100, finishedAt: new Date(), result: { imported: 0, skipped: 0, labeled: 0 } as any },\n })\n );\n return;\n }\n\n await updateJobProgress(tenantId, jobId, 35, `Parsed ${parsedCases.length} cases \u2014 creating suite`);\n\n // Auto-create suite from filename when suiteId === 'new'\n if (!suiteId || suiteId === 'new') {\n const suiteName = filename.replace(/\\.[^/.]+$/, '') || 'Imported Tests';\n const suite = await withTenant(tenantId, (tx) =>\n (tx as any).testSuite.create({\n data: { tenantId, projectId, name: suiteName },\n })\n );\n suiteId = (suite as any).id;\n }\n\n await updateJobProgress(tenantId, jobId, 50, `Importing ${parsedCases.length} cases`);\n\n const { created, skipped } = await AppBrain.importTestCasesFromArtifact(\n tenantId, projectId, suiteId, parsedCases, { duplicateMode: 'skip', artifactId }\n );\n\n let labeled = 0;\n if (autoLabel && created.length > 0) {\n await updateJobProgress(tenantId, jobId, 75, `AI labeling ${created.length} cases`);\n try {\n const labelInput = created.map((c: any) => ({\n id: c.id, title: c.title,\n description: c.description ?? '',\n stepsText: (Array.isArray(c.steps) ? c.steps.join(' ') : String(c.steps ?? '')).slice(0, 200),\n }));\n const labelResults = await labelTestCasesBatch(labelInput, tenantId);\n // Persist labels back to each test case\n await Promise.all(\n labelResults.map((lr: any) =>\n withTenant(tenantId, (tx) =>\n tx.testCase.update({\n where: { id: lr.id },\n data: {\n priority: lr.priority ?? undefined,\n kind: lr.kind ?? undefined,\n tags: lr.tags ?? undefined,\n status: lr.needsReview ? 'PENDING_REVIEW' : undefined,\n },\n })\n ).catch(() => { }) // non-fatal per-case\n )\n );\n labeled = labelResults.length;\n } catch {\n // non-fatal \u2014 import succeeded even if labeling fails\n }\n }\n\n // Auto-generate AI test data for imported cases that have recorded form fields (testDataInline)\n const casesNeedingData = created.filter((c: any) => {\n const inline = (c as any).testDataInline;\n return inline && typeof inline === 'object' && Object.keys(inline).length > 0;\n });\n if (casesNeedingData.length > 0) {\n const cap = Math.min(casesNeedingData.length, 20);\n await updateJobProgress(tenantId, jobId, 90, `Generating test data for ${cap} cases`);\n const { generateTestData, refreshTestDataSkillFile } = await import('@detiq/agents');\n for (let i = 0; i < cap; i++) {\n const c = casesNeedingData[i];\n await generateTestData({ tenantId, projectId, testCaseId: c.id, jobId }).catch((e: any) => {\n logger.warn({ err: e?.message, testCaseId: c.id }, '[import] test data generation failed');\n });\n }\n refreshTestDataSkillFile(tenantId, projectId).catch(console.error);\n }\n\n if (created.length > 0) {\n refreshTestCasesSkillFile(tenantId, projectId).catch(console.error);\n }\n\n await updateJobProgress(tenantId, jobId, 98, `Done \u2014 ${created.length} imported`);\n\n // Write final result and mark job DONE\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE',\n progress: 100,\n finishedAt: new Date(),\n result: {\n imported: created.length,\n skipped: skipped ?? 0,\n labeled,\n suiteId,\n artifactId,\n artifactName: filename,\n } as any,\n },\n })\n );\n aggregateJobTokens(tenantId, jobId, projectId, jobStart).catch(console.error);\n}\n\n// Generate test cases for a single screen, enqueued by NEW_PAGES_PIPELINE.\n// Uses the same DualContextOrchestrator path as the in-process GENERATE_TESTS route.\nasync function processGenerateScreenTestsJob(jobId: string, config: any) {\n const { tenantId, projectId, screenId, repoConnectionId, triggerAlignedScripts } = config;\n const jobStartedAt = new Date();\n try {\n await withTenant(tenantId, (tx: any) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: jobStartedAt } })\n );\n\n const { DualContextOrchestrator } = await import('@detiq/agents');\n await updateJobProgress(tenantId, jobId, 10, 'Generating test cases for screen');\n\n const crawlCfg = await AppBrain.getCrawlConfig(tenantId, projectId).catch(() => null);\n const deviceProfiles: string[] = (crawlCfg as any)?.deviceProfiles ?? ['DESKTOP'];\n\n for (const viewport of deviceProfiles) {\n await DualContextOrchestrator.execute({ tenantId, projectId, targetScreenId: screenId, jobId, viewport });\n }\n\n await updateJobProgress(tenantId, jobId, 80, 'Test cases generated');\n\n // Optionally kick framework-aligned script generation for this screen's test cases\n if (triggerAlignedScripts && repoConnectionId) {\n const conn: any = await withTenant(tenantId, (tx: any) =>\n tx.gitHubRepoConnection.findFirst({ where: { id: repoConnectionId, tenantId } })\n );\n if (conn?.styleProfile) {\n const { generateScriptAligned, mapWithConcurrency, SCRIPT_GEN_CONCURRENCY } = await import('@detiq/agents');\n const cases = await withTenant(tenantId, (tx: any) =>\n tx.testCase.findMany({ where: { tenantId, projectId, screenId, status: 'APPROVED' }, select: { id: true }, take: 50 })\n );\n await mapWithConcurrency(cases as any[], SCRIPT_GEN_CONCURRENCY, (tc: any) =>\n generateScriptAligned({\n tenantId,\n projectId,\n testCaseId: tc.id,\n styleProfile: conn.styleProfile,\n pageObjectInventory: conn.pageObjectInventory ?? undefined,\n }).catch(() => { })\n );\n }\n }\n\n await withTenant(tenantId, (tx: any) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', progress: 100, finishedAt: new Date() } })\n );\n aggregateJobTokens(tenantId, jobId, projectId, jobStartedAt).catch(console.error);\n } catch (err: any) {\n const msg = err?.message ?? String(err);\n logger.error({ jobId, screenId, err: msg }, 'processGenerateScreenTestsJob failed');\n await withTenant(tenantId, (tx: any) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', error: msg.slice(0, 500), finishedAt: new Date() } })\n ).catch(() => { });\n }\n}\n\ntype CaptureStepStatus = 'PASSED' | 'FAILED' | 'HEALED' | 'SKIPPED';\ntype CaptureStepResult = {\n stepNumber: number;\n description: string;\n status: CaptureStepStatus;\n screenshotPath?: string;\n screenshotUrl?: string;\n screenId?: string;\n url?: string;\n elementsCount?: number;\n healed?: boolean;\n healTier?: 'mechanical' | 'ai';\n healReasoning?: string;\n error?: string;\n};\n\nfunction captureStepText(step: unknown): string {\n if (typeof step === 'string') return step;\n if (!step || typeof step !== 'object') return '';\n const obj = step as Record<string, unknown>;\n return [obj.action, obj.text, obj.label, obj.data, obj.expected]\n .filter((v): v is string => typeof v === 'string' && v.trim().length > 0)\n .join(' -> ');\n}\n\nfunction firstQuoted(text: string): string | undefined {\n return (text.match(/\"([^\"]+)\"/)?.[1] ?? text.match(/'([^']+)'/)?.[1])?.trim();\n}\n\nfunction fallbackExecutionPlan(rawSteps: unknown[]): ExecutionStep[] {\n const plan: ExecutionStep[] = [];\n for (const raw of rawSteps) {\n const text = captureStepText(raw).trim();\n if (!text) continue;\n const stepNumber = plan.length + 1;\n const url = text.match(/https?:\\/\\/[^\\s\"']+/)?.[0];\n\n if (/^navigate\\b|^go to\\b|^open\\b/i.test(text) && url) {\n plan.push({ stepNumber, description: text, action: 'navigate', url });\n continue;\n }\n\n const urlContains = text.match(/verify url contains:\\s*[\"']?([^\"']+)[\"']?/i);\n if (urlContains) {\n plan.push({ stepNumber, description: text, action: 'assert_url', value: urlContains[1].trim() });\n continue;\n }\n\n const inputMatch = text.match(/(?:enter|type|fill)\\s+[\"']([^\"']+)[\"']\\s+(?:in|into)\\s+(.+?)(?:\\s+field|\\s+input|\\s+box|\\s+area)?(?:\\s*\u2192.*)?$/i);\n if (inputMatch) {\n const selector = inputMatch[2].replace(/^(the\\s+)?/i, '').replace(/\\s+(field|input|box|area)$/i, '').trim();\n plan.push({ stepNumber, description: text, action: 'fill', selectorStrategy: 'label', selector, value: inputMatch[1].trim() });\n continue;\n }\n\n const quoted = firstQuoted(text);\n if (/^(click|tap|navigate menu|check|select)\\b/i.test(text) && quoted) {\n plan.push({ stepNumber, description: text, action: 'click', selectorStrategy: 'text', selector: quoted });\n continue;\n }\n if (/^(verify|assert|expect)\\b/i.test(text) && quoted) {\n plan.push({ stepNumber, description: text, action: 'assert_visible', selectorStrategy: 'text', selector: quoted });\n continue;\n }\n if (/execute script/i.test(text)) {\n const script = text.split(':').slice(1).join(':').replace(/\\s*\u2192.*$/, '').trim();\n if (script) plan.push({ stepNumber, description: text, action: 'script', value: script });\n }\n }\n return plan;\n}\n\nfunction screenNameFromUrl(url: string, fallback: string): string {\n try {\n const u = new URL(url);\n return u.pathname.replace(/\\/+$/, '').split('/').filter(Boolean).pop() || u.hostname || fallback;\n } catch {\n return fallback;\n }\n}\n\nasync function processCaptureStepsJob(jobId: string, config: any) {\n const { tenantId, projectId, testCaseId } = config as { tenantId: string; projectId: string; testCaseId: string };\n const screenshotDir = config.screenshotDir || platformConfig.screenshotDir || process.env.SCREENSHOT_DIR || '/tmp/zeta-screenshots';\n await fs.mkdir(screenshotDir, { recursive: true });\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: new Date(), progress: 2 } })\n );\n\n let browser: any;\n try {\n await updateJobProgress(tenantId, jobId, 5, 'Loading test case');\n const [tc, crawlCfg, tenantStorageCfg] = await Promise.all([\n AppBrain.getTestCaseForExecution(tenantId, testCaseId) as Promise<any>,\n AppBrain.getCrawlConfig(tenantId, projectId).catch(() => null),\n getTenantStorageConfig(tenantId).catch(() => null),\n ]);\n if (!tc) throw new Error('Test case not found');\n\n const rawSteps = Array.isArray(tc.steps) ? tc.steps : [];\n if (rawSteps.length === 0) throw new Error('Test case has no steps to capture');\n\n // Resolve env tokens from testDataInline so {{fze.env.URL}} \u2192 concrete URL before execution plan\n const _tdi = ((tc as any).testDataInline ?? {}) as Record<string, unknown>;\n const _tdiStartUrl = typeof _tdi['startUrl'] === 'string' && (_tdi['startUrl'] as string).startsWith('http')\n ? (_tdi['startUrl'] as string) : '';\n const _recordedUrls = Array.isArray(_tdi['recordedUrls']) ? (_tdi['recordedUrls'] as unknown[]).map(String) : [];\n const _recordedStartUrl = _recordedUrls.find((u) => u.startsWith('http')) ?? '';\n\n const baseUrl = config.baseUrl || _tdiStartUrl || _recordedStartUrl || tc?.screen?.project?.appUrl || crawlCfg?.appUrl || '';\n\n // Build env token \u2192 concrete value map from testDataInline for step text substitution\n const _envTokenMap: Record<string, string> = {};\n for (const [k, v] of Object.entries(_tdi)) {\n if (typeof v === 'string' && v.startsWith('http')) _envTokenMap[k] = v;\n }\n if (_recordedStartUrl && !_envTokenMap['startUrl']) _envTokenMap['startUrl'] = _recordedStartUrl;\n\n // Replace {{fze.env.X}} and [env:X] tokens in step texts with concrete values from testDataInline.\n // IMPORTANT: only substitute if we have a concrete value for that key.\n // Fall back to [key] placeholder \u2014 never fall back to baseUrl (would inject URL into credential fields).\n const resolveStepText = (text: string): string =>\n text\n .replace(/\\{\\{\\s*fze\\.env\\.([A-Za-z_][A-Za-z0-9_]*)\\s*\\}\\}/g, (_m, k) => _envTokenMap[k] ?? `[${k}]`)\n .replace(/\\[env:([A-Za-z_][A-Za-z0-9_]*)\\]/g, (_m, k) => _envTokenMap[k] ?? `[${k}]`);\n\n const stepTexts = rawSteps.map(captureStepText).filter(Boolean).map(resolveStepText);\n if (stepTexts.length === 0) throw new Error('Test case has no steps to capture');\n\n let plan = await generateExecutionPlan(tenantId, { ...tc, steps: stepTexts }, baseUrl).catch(() => []);\n plan = Array.isArray(plan)\n ? plan.filter(Boolean).map((step: any, idx: number) => ({ ...step, stepNumber: idx + 1 }))\n : [];\n if (plan.length === 0) plan = fallbackExecutionPlan(rawSteps);\n if (plan.length === 0) throw new Error('Could not generate execution plan from steps');\n\n const { chromium } = await import('playwright');\n browser = await chromium.launch({\n headless: true,\n ...(process.env.PLAYWRIGHT_EXECUTABLE_PATH ? { executablePath: process.env.PLAYWRIGHT_EXECUTABLE_PATH } : {}),\n });\n let parsedStorageState: any;\n try {\n parsedStorageState = config.storageState ? JSON.parse(config.storageState) : crawlCfg?.storageState ? JSON.parse(crawlCfg.storageState) : undefined;\n } catch {\n parsedStorageState = undefined;\n }\n const context = await browser.newContext({ viewport: { width: 1280, height: 800 }, ...(parsedStorageState ? { storageState: parsedStorageState } : {}) });\n const page = await context.newPage();\n const aiHealingEnabled = await isFeatureEnabled(tenantId, 'AI_SELF_HEALING').catch(() => false);\n\n const stepResults: CaptureStepResult[] = [];\n const screens: Array<{ stepNumber: number; screenId: string; url: string; screenshotPath?: string; screenshotUrl?: string; isNewScreen: boolean }> = [];\n const locators: any[] = [];\n const seenLocatorExpressions = new Set<string>();\n const seenScreenKeys = new Set<string>();\n let lastStateKey = '';\n\n for (let i = 0; i < plan.length; i++) {\n const step = plan[i];\n const description = step.description || stepTexts[i] || `Step ${i + 1}`;\n const stepNumber = i + 1;\n const result: CaptureStepResult = { stepNumber, description, status: 'FAILED' };\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n progress: Math.min(90, Math.round((i / Math.max(plan.length, 1)) * 90)),\n result: { currentStep: { stepNumber, description } } as any,\n heartbeatAt: new Date(),\n },\n })\n ).catch(() => { });\n\n try {\n const heal = await executeExecutionStep(page, step, aiHealingEnabled ? { healCtx: { tenantId, projectId } } : {});\n if (heal.healed) {\n result.healed = true;\n result.healTier = heal.healedVia;\n result.healReasoning = heal.healReasoning;\n }\n await page.waitForLoadState('networkidle', { timeout: parseInt(process.env.TEST_NETWORK_IDLE_TIMEOUT_MS ?? '2000', 10) }).catch(() => { });\n result.status = result.healed ? 'HEALED' : 'PASSED';\n } catch (err: any) {\n result.status = step.optional ? 'SKIPPED' : 'FAILED';\n result.error = String(err?.message ?? err).slice(0, 300);\n }\n\n const currentUrl = page.url();\n result.url = currentUrl;\n const ssFilename = `${testCaseId}-capture-step-${stepNumber}${result.status === 'FAILED' ? '-err' : ''}.png`;\n const ssPath = path.join(screenshotDir, ssFilename);\n await page.screenshot({ path: ssPath, fullPage: false }).catch(() => { });\n result.screenshotPath = ssPath;\n\n let cloudUrl: string | undefined;\n if (tenantStorageCfg) {\n const key = buildArtifactKey(tenantId, projectId, 'screenshots', ssFilename, tenantStorageCfg.prefix);\n cloudUrl = (await uploadScreenshotToTenantStorage(tenantStorageCfg, ssPath, key).catch(() => undefined)) ?? undefined;\n if (cloudUrl) result.screenshotUrl = cloudUrl;\n }\n\n const elements = await domFallbackElements(page).catch(() => []);\n result.elementsCount = elements.length;\n const stateHash = hashContent(elements.map((el) => `${el.role}:${el.meaning}:${el.notes ?? ''}`).join('|'));\n const screenKey = `${currentUrl || 'about:blank'}:${stateHash}`;\n const shouldPersistScreen = currentUrl && currentUrl !== 'about:blank' && (i === 0 || screenKey !== lastStateKey || !seenScreenKeys.has(screenKey));\n lastStateKey = screenKey;\n\n if (shouldPersistScreen) {\n const screenName = screenNameFromUrl(currentUrl, tc.title ?? `Step ${stepNumber}`);\n const screen = await AppBrain.saveScreen(tenantId, projectId, screenName, currentUrl, jobId, i === 0 ? 'ENTRY_POINT' : 'LINK_FOLLOW').catch(() => null) as any;\n if (screen?.id) {\n result.screenId = screen.id;\n await AppBrain.saveScreenshot(tenantId, projectId, screen.id, ssPath, screen.version ?? 1, 'DESKTOP', cloudUrl).catch(() => { });\n for (const el of elements) {\n await AppBrain.saveElement(\n tenantId, projectId, screen.id,\n el.meaning, el.role, el.expectedData, el.notes,\n undefined, true, el.boundingRect, el.ariaState, el.parentLandmark,\n ).catch(() => { });\n }\n for (const loc of elementsToPlaywrightLocators(elements as any)) {\n if (seenLocatorExpressions.has(loc.expression)) continue;\n seenLocatorExpressions.add(loc.expression);\n locators.push(loc);\n }\n if (!seenScreenKeys.has(screenKey)) {\n screens.push({ stepNumber, screenId: screen.id, url: currentUrl, screenshotPath: ssPath, screenshotUrl: cloudUrl, isNewScreen: true });\n seenScreenKeys.add(screenKey);\n }\n }\n }\n\n stepResults.push(result);\n if (result.status === 'FAILED') break;\n }\n\n if (locators.length > 0) {\n await AppBrain.saveLocatorsJson(tenantId, testCaseId, locators as any).catch(() => { });\n }\n\n const firstScreen = screens[0];\n await withTenant(tenantId, (tx) =>\n (tx as any).testCase.update({\n where: { id: testCaseId },\n data: {\n ...(firstScreen?.screenId ? { screenId: firstScreen.screenId } : {}),\n captureStepScreenshots: stepResults as any,\n },\n })\n ).catch(() => { });\n\n const finalResult = {\n stepResults,\n screens,\n locators,\n totalSteps: plan.length,\n capturedScreens: screens.length,\n };\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', progress: 100, result: finalResult as any, finishedAt: new Date(), heartbeatAt: new Date() },\n })\n );\n } catch (err: any) {\n const error = String(err?.message ?? err);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', error, finishedAt: new Date(), heartbeatAt: new Date() } })\n ).catch(() => { });\n logger.error({ jobId, testCaseId, err: error }, 'processCaptureStepsJob failed');\n throw err;\n } finally {\n await browser?.close?.().catch(() => { });\n }\n}\n\n// \u2500\u2500 Multi-role crawl orchestrator \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Runs a primary CRAWL to discover all screens, then fans out one ROLE_CRAWL\n// job per credential profile. Role jobs run concurrently via the worker pool.\n\nasync function processMultiRoleCrawlJob(jobId: string, config: any) {\n const { tenantId, projectId } = config;\n try {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.updateMany({\n where: { id: jobId, status: { not: 'CANCELLED' } },\n data: { status: 'RUNNING', startedAt: new Date(), heartbeatAt: new Date(), workerId: WORKER_ID },\n })\n );\n\n await updateJobProgress(tenantId, jobId, 5, 'Loading project crawl config\u2026');\n\n // Load saved project crawlConfig for primary crawl parameters\n const proj = await withTenant(tenantId, (tx) =>\n (tx as any).project.findUnique({ where: { id: projectId }, select: { crawlConfig: true } })\n ) as { crawlConfig: any } | null;\n const savedCrawlConfig = proj?.crawlConfig ?? {};\n\n const credentialProfiles: Array<{ id: string; name: string; username: string; password: string }> =\n config.credentialProfiles ?? [];\n\n // \u2500\u2500 Step 1: enqueue primary CRAWL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n await updateJobProgress(tenantId, jobId, 10, 'Enqueuing primary crawl\u2026');\n const primaryJobId = await enqueueJob('CRAWL', {\n tenantId,\n projectId,\n appUrl: config.appUrl || savedCrawlConfig.appUrl,\n credentials: savedCrawlConfig.username && savedCrawlConfig.password\n ? { username: savedCrawlConfig.username, password: savedCrawlConfig.password, ...(savedCrawlConfig.totpSecret ? { totpSecret: savedCrawlConfig.totpSecret } : {}) }\n : undefined,\n storageState: savedCrawlConfig.storageState,\n screenshotDir: platformConfig.screenshotDir,\n startUrls: savedCrawlConfig.seedUrls?.length ? savedCrawlConfig.seedUrls : undefined,\n deviceProfiles: savedCrawlConfig.deviceProfiles ?? ['DESKTOP'],\n loginInstructions: savedCrawlConfig.loginInstructions,\n proxyUrl: savedCrawlConfig.proxyUrl,\n useSitemap: true,\n sitemapUrl: savedCrawlConfig.sitemapUrl,\n });\n logger.info({ primaryJobId, projectId }, '[multi-role-crawl] primary CRAWL enqueued');\n\n // \u2500\u2500 Step 2: poll until primary CRAWL is done (2-hour timeout) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const PRIMARY_TIMEOUT_MS = 2 * 60 * 60 * 1000;\n const POLL_INTERVAL_MS = 10_000;\n const deadline = Date.now() + PRIMARY_TIMEOUT_MS;\n let primaryStatus = 'QUEUED';\n while (Date.now() < deadline) {\n const primaryJob = await prismaAdmin.platformJob.findUnique({\n where: { id: primaryJobId },\n select: { status: true },\n }).catch(() => null);\n primaryStatus = primaryJob?.status ?? 'UNKNOWN';\n if (primaryStatus === 'DONE' || primaryStatus === 'FAILED' || primaryStatus === 'CANCELLED') break;\n await updateJobProgress(tenantId, jobId, 20, `Waiting for primary crawl (${primaryStatus})\u2026`);\n await new Promise((res) => setTimeout(res, POLL_INTERVAL_MS));\n }\n\n if (primaryStatus !== 'DONE') {\n throw new Error(`Primary crawl ${primaryJobId} ended with status ${primaryStatus} \u2014 aborting role crawls`);\n }\n\n // \u2500\u2500 Step 3: collect all known screen URLs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n await updateJobProgress(tenantId, jobId, 50, 'Collecting screen URLs for role crawls\u2026');\n const screens = await prismaAdmin.screen.findMany({\n where: { tenantId, projectId, status: 'ACTIVE' },\n select: { id: true, url: true },\n }).catch(() => [] as Array<{ id: string; url: string | null }>);\n const screenUrls = screens.filter((s) => s.url).map((s) => s.url as string);\n logger.info({ screenCount: screenUrls.length, projectId }, '[multi-role-crawl] screen URLs collected');\n\n // \u2500\u2500 Step 4: enqueue one ROLE_CRAWL per credential profile \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (credentialProfiles.length === 0) {\n logger.warn({ projectId, jobId }, '[multi-role-crawl] no credential profiles \u2014 nothing to role-crawl');\n }\n\n const roleJobs: Array<{ roleName: string; jobId: string }> = [];\n for (const profile of credentialProfiles) {\n if (!profile.username || !profile.password) continue;\n const roleJobId = await enqueueJob('ROLE_CRAWL', {\n tenantId,\n projectId,\n appUrl: config.appUrl || savedCrawlConfig.appUrl,\n credentials: { username: profile.username, password: profile.password },\n roleProfileName: profile.name,\n parentJobId: jobId,\n selectiveUrls: config.fullReCrawl ? undefined : (screenUrls.length > 0 ? screenUrls : undefined),\n startUrls: config.fullReCrawl ? (savedCrawlConfig.seedUrls ?? []) : undefined,\n maxScreens: config.fullReCrawl ? undefined : screenUrls.length + 10,\n screenshotDir: platformConfig.screenshotDir,\n storageState: savedCrawlConfig.storageState,\n deviceProfiles: savedCrawlConfig.deviceProfiles ?? ['DESKTOP'],\n loginInstructions: savedCrawlConfig.loginInstructions,\n proxyUrl: savedCrawlConfig.proxyUrl,\n });\n roleJobs.push({ roleName: profile.name, jobId: roleJobId });\n logger.info({ roleJobId, roleName: profile.name, projectId }, '[multi-role-crawl] ROLE_CRAWL enqueued');\n }\n\n // \u2500\u2500 Step 5: poll until ALL role crawls complete \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const ROLE_TIMEOUT_MS = 2 * 60 * 60 * 1000;\n const roleDeadline = Date.now() + ROLE_TIMEOUT_MS;\n const roleResults: Array<{ roleName: string; jobId: string; status: string; screensTagged?: number }> = [];\n\n while (Date.now() < roleDeadline && roleJobs.length > 0) {\n const pending: typeof roleJobs = [];\n for (const rj of roleJobs) {\n const rJob = await prismaAdmin.platformJob.findUnique({\n where: { id: rj.jobId },\n select: { status: true, result: true },\n }).catch(() => null);\n const st = rJob?.status ?? 'UNKNOWN';\n if (st === 'DONE' || st === 'FAILED' || st === 'CANCELLED') {\n roleResults.push({\n roleName: rj.roleName,\n jobId: rj.jobId,\n status: st,\n screensTagged: (rJob?.result as any)?.screensTagged,\n });\n } else {\n pending.push(rj);\n }\n }\n roleJobs.length = 0;\n roleJobs.push(...pending);\n if (roleJobs.length === 0) break;\n const pct = Math.round(60 + ((roleResults.length / (roleResults.length + roleJobs.length)) * 35));\n await updateJobProgress(tenantId, jobId, pct, `Role crawls: ${roleResults.length} done, ${roleJobs.length} pending\u2026`);\n await new Promise((res) => setTimeout(res, POLL_INTERVAL_MS));\n }\n\n // Any still-pending jobs timed out \u2014 record them as timed out\n for (const rj of roleJobs) {\n roleResults.push({ roleName: rj.roleName, jobId: rj.jobId, status: 'TIMEOUT' });\n }\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE',\n progress: 100,\n finishedAt: new Date(),\n result: { primaryJobId, roleJobs: roleResults } as any,\n },\n })\n );\n logger.info({ jobId, primaryJobId, roleCount: roleResults.length, projectId }, '[multi-role-crawl] completed');\n } catch (err: any) {\n const msg = String(err?.message ?? err);\n logger.error({ jobId, err: msg }, '[multi-role-crawl] failed');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', error: msg, finishedAt: new Date() } })\n ).catch(() => { });\n }\n}\n\n// \u2500\u2500 Role-specific sub-crawl \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Crawls the app as a specific credential profile and tags all discovered\n// screens with the role name in the roleAccess array column.\n\nasync function processRoleCrawlJob(jobId: string, config: any) {\n const { tenantId, projectId } = config;\n try {\n const claimed = await withTenant(tenantId, (tx) =>\n tx.platformJob.updateMany({\n where: { id: jobId, status: { not: 'CANCELLED' } },\n data: { status: 'RUNNING', startedAt: new Date(), heartbeatAt: new Date(), workerId: WORKER_ID },\n })\n );\n if (claimed.count === 0) return;\n\n const controller = new AbortController();\n runningControllers.set(jobId, controller);\n const heartbeatTimer = setInterval(() => void heartbeatJob(tenantId, jobId), JOB_HEARTBEAT_INTERVAL_MS);\n\n const onProgress = async (pct: number, step: string) => {\n await updateJobProgress(tenantId, jobId, pct, step);\n };\n\n try {\n // Load project crawlConfig for proxy, storageState overrides\n const proj = await withTenant(tenantId, (tx) =>\n (tx as any).project.findUnique({ where: { id: projectId }, select: { crawlConfig: true } })\n ) as { crawlConfig: any } | null;\n const savedCrawlConfig = proj?.crawlConfig ?? {};\n\n // Build runtime credentials with TOTP support\n const runtimeCreds = config.credentials\n ? {\n ...config.credentials,\n ...(config.credentials.totpSecret\n ? { getOneTimeCode: async () => generateTOTP(config.credentials.totpSecret) }\n : {}),\n }\n : undefined;\n\n const storageConfigForCrawl = await getTenantStorageConfig(tenantId).catch(() => null);\n\n const result = await crawlProject({\n ...config,\n credentials: runtimeCreds,\n storageState: config.storageState ?? savedCrawlConfig.storageState,\n proxyUrl: config.proxyUrl ?? savedCrawlConfig.proxyUrl,\n loginInstructions: config.loginInstructions ?? savedCrawlConfig.loginInstructions,\n abortSignal: controller.signal,\n jobId,\n storageConfig: storageConfigForCrawl,\n }, onProgress);\n\n clearInterval(heartbeatTimer);\n runningControllers.delete(jobId);\n\n // Tag discovered screens with this role\n const roleName = config.roleProfileName as string;\n const discovered: any[] = (result as any).discovered ?? [];\n const roleScreenIds = discovered.filter((d: any) => d.screenId).map((d: any) => d.screenId as string);\n\n if (roleScreenIds.length > 0 && roleName) {\n await prismaAdmin.$executeRaw`\n UPDATE \"Screen\"\n SET \"roleAccess\" = array(SELECT DISTINCT unnest(array_append(\"roleAccess\", ${roleName}::text)))\n WHERE id = ANY(${roleScreenIds}::text[])\n AND \"tenantId\" = ${tenantId}\n `.catch((e: any) => logger.warn({ err: e?.message, roleName, count: roleScreenIds.length }, '[role-crawl] roleAccess tag failed (non-fatal)'));\n }\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE',\n progress: 100,\n finishedAt: new Date(),\n result: { screensTagged: roleScreenIds.length, roleName } as any,\n },\n })\n );\n logger.info({ jobId, roleName, screensTagged: roleScreenIds.length, projectId }, '[role-crawl] completed');\n } catch (err: any) {\n clearInterval(heartbeatTimer);\n runningControllers.delete(jobId);\n const aborted = String(err).includes('CRAWL_ABORTED');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: aborted ? 'CANCELLED' : 'FAILED',\n error: aborted ? 'Stopped by user' : String(err),\n finishedAt: new Date(),\n },\n })\n );\n } finally {\n runningControllers.delete(jobId);\n }\n } catch (err: any) {\n const msg = String(err?.message ?? err);\n logger.error({ jobId, err: msg }, '[role-crawl] outer failure');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', error: msg, finishedAt: new Date() } })\n ).catch(() => { });\n }\n}\n\nasync function processGenerateScriptJob(jobId: string, config: any): Promise<void> {\n const { tenantId, projectId, language } = config;\n try {\n await updateJobProgress(tenantId, jobId, 1, 'Loading test cases\u2026').catch(() => {});\n\n if (config.packId) {\n // Pack script (regression / smoke / sanity / etc.)\n const { generatePackScript } = await import('@detiq/agents');\n const result = await generatePackScript({ tenantId, projectId, packType: config.packId, language });\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', progress: 100, finishedAt: new Date(), result: result as any },\n })\n );\n } else {\n // Suite script\n const { generateSuiteScript } = await import('@detiq/agents');\n let progressChain: Promise<void> = Promise.resolve();\n const result = await generateSuiteScript({\n tenantId, projectId, suiteId: config.suiteId, language,\n caseIds: config.caseIds ?? undefined,\n useV2: config.useV2,\n styleProfile: config.repoStyleProfile ?? undefined,\n pageObjectInventory: config.repoPageObjects ?? undefined,\n jobId,\n onProgress: (done: number, total: number) => {\n progressChain = progressChain\n .then(() => updateJobProgress(tenantId, jobId, Math.floor((done / total) * 95), `Generated ${done}/${total} scripts`))\n .catch(() => {});\n },\n });\n await progressChain.catch(() => {});\n await import('@detiq/agents')\n .then(({ refreshScriptInventory }) => refreshScriptInventory(tenantId, projectId))\n .catch((e: any) => logger.warn({ err: e?.message }, '[generate-script] refreshScriptInventory failed'));\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', progress: 100, finishedAt: new Date(), result: result as any },\n })\n );\n }\n } catch (e: any) {\n await finishTrackedJob(tenantId, jobId, e?.message ?? 'generate-script failed');\n }\n}\n\nfunction queueImportedArtifactSkillRefresh(tenantId: string, projectId: string, reason: string): void {\n setImmediate(() => {\n import('@detiq/agents')\n .then((agentsModule) => {\n const agents = agentsModule as any;\n return Promise.allSettled([\n agents.refreshArtifactKnowledgeSkillFile(tenantId, projectId),\n agents.refreshRequirementsCoverageSkillFile(tenantId, projectId),\n ]);\n })\n .then((results) => {\n results?.forEach((result) => {\n if (result.status === 'rejected') {\n console.warn(`[artifacts] skill refresh failed after ${reason}:`, result.reason?.message ?? result.reason);\n }\n });\n })\n .catch((e) => console.warn(`[artifacts] skill refresh failed after ${reason}:`, e?.message ?? e));\n });\n}\n\n// Registers the type\u2192processor dispatch table used by the durable worker (both the\n// enqueueJob() fast-path kick and startJobWorkerLoop()'s poll). Declared at module\n// bottom so it can reference every process*Job function by name; function\n// declarations are hoisted, so this could technically live anywhere in the file,\n// but keeping it here alongside the last definition keeps the mapping easy to audit\n// against the actual functions above.\nconst CONTENT_DETECTED_DOC_TYPES = new Set(['FUNCTIONIZE_TC', 'SELENIUM_SCRIPT', 'ENV_CONFIG']);\nconst AI_DOC_TYPES = new Set([\n 'REQUIREMENTS', 'USER_STORY', 'TEST_CASES', 'TEST_DATA', 'AUTOMATION_SCRIPT', 'API_SPEC', 'ARCHITECTURE', 'REPORT', 'OTHER',\n 'SPREADSHEET', 'DATASHEET',\n]);\nconst AI_DOC_TYPE_ALIASES: Record<string, string> = {\n REQUIREMENT: 'REQUIREMENTS', SPEC: 'REQUIREMENTS', SPECIFICATION: 'REQUIREMENTS',\n USER_STORIES: 'USER_STORY', STORY: 'USER_STORY',\n TEST_CASE: 'TEST_CASES', TESTCASES: 'TEST_CASES',\n TESTDATA: 'TEST_DATA', SCRIPT: 'AUTOMATION_SCRIPT', AUTOMATION: 'AUTOMATION_SCRIPT',\n};\nfunction normalizeAiDocType(raw: unknown): string {\n const key = String(raw ?? '').trim().toUpperCase().replace(/[\\s-]+/g, '_');\n const mapped = AI_DOC_TYPE_ALIASES[key] ?? key;\n return AI_DOC_TYPES.has(mapped) ? mapped : 'OTHER';\n}\n\nasync function processArtifactIntelJob(jobId: string, config: any) {\n const { tenantId, projectId, artifactId, artifactName, isReprocess } = config;\n try {\n await updateJobProgress(tenantId, jobId, 10, 'Downloading artifact content');\n \n const artifact = await withTenant(tenantId, (tx) =>\n (tx as any).projectArtifact.findUnique({ where: { id: artifactId }, select: { storageKey: true, docType: true } })\n ) as any;\n \n if (!artifact || !artifact.storageKey) {\n await updateJobProgress(tenantId, jobId, 100, 'Artifact deleted or missing content, skipping.');\n await withTenant(tenantId, (tx) =>\n (tx as any).platformJob.update({ where: { id: jobId }, data: { status: 'DONE', finishedAt: new Date() } })\n );\n return;\n }\n\n const storageCfg = await getTenantStorageConfig(tenantId);\n if (!storageCfg) {\n throw new Error('Tenant storage configuration missing.');\n }\n const contentBuffer = await downloadBufferFromTenantStorage(storageCfg, artifact.storageKey);\n const content = contentBuffer.toString('utf8');\n\n await updateJobProgress(tenantId, jobId, 40, 'Generating intelligence');\n\n const intel = await generateArtifactIntelligence(tenantId, projectId, artifactId, content, artifactName, jobId);\n \n const finalDocType = artifact.docType && CONTENT_DETECTED_DOC_TYPES.has(artifact.docType)\n ? artifact.docType\n : normalizeAiDocType(intel.docType);\n\n if (finalDocType !== intel.docType) {\n await withTenant(tenantId, (tx) =>\n (tx as any).projectArtifact.update({ where: { id: artifactId }, data: { docType: finalDocType } })\n ).catch((err: any) => console.warn(`[artifact-intel] docType fix-up failed for \"${artifactName}\":`, err?.message));\n intel.docType = finalDocType;\n }\n\n await updateJobProgress(tenantId, jobId, 90, 'Saving results');\n \n await withTenant(tenantId, (tx) =>\n (tx as any).platformJob.update({ where: { id: jobId }, data: { result: { artifactId, artifactName, summary: intel.summary, keywords: intel.keywords, docType: intel.docType }, status: 'DONE', finishedAt: new Date(), progress: 100 } })\n );\n\n queueImportedArtifactSkillRefresh(tenantId, projectId, isReprocess ? 'artifact-reprocess' : 'artifact-intel');\n } catch (err: any) {\n const msg = String(err?.message ?? err);\n logger.error({ jobId, err: msg }, '[artifact-intel] failed');\n await withTenant(tenantId, (tx) =>\n (tx as any).projectArtifact.update({ where: { id: artifactId }, data: { status: 'ERROR', summary: 'AI analysis failed' } })\n ).catch(() => { });\n await withTenant(tenantId, (tx) =>\n (tx as any).platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', finishedAt: new Date(), error: msg.slice(0, 1000) } })\n ).catch(() => { });\n queueImportedArtifactSkillRefresh(tenantId, projectId, 'artifact-intel-failed');\n }\n}\n\njobDispatchTable = {\n ARTIFACT_INTEL: processArtifactIntelJob,\n CRAWL: processJob,\n CAPTURE_STEPS: processCaptureStepsJob,\n DISCOVER: processDiscoverJob,\n MONITOR: processMonitorJob,\n NATIVE_CRAWL: processNativeCrawlJob,\n API_TESTS: processApiTestsJob,\n SECURITY_TESTS: processSecurityTestsJob,\n INTEGRATION_TESTS: processIntegrationTestsJob,\n FIGMA_IMPORT: processFigmaImportJob,\n GENERATE_SKILL_FILES: processGenerateSkillFilesJob,\n GENERATE_TESTS_FROM_REQS: processGenerateTestsFromReqsJob,\n GENERATE_SCREEN_TESTS: processGenerateScreenTestsJob,\n RUN_TEST_SUITE: processRunTestSuiteJob,\n REPO_SCAN: processRepoScanJob,\n IMPORT_TESTS: processImportTestsJob,\n BATCH_GENERATE_ALIGNED: processBatchGenerateAlignedJob,\n CREATE_PR: processCreatePRJob,\n NEW_PAGES_PIPELINE: processNewPagesPipelineJob,\n MULTI_ROLE_CRAWL: processMultiRoleCrawlJob,\n ROLE_CRAWL: processRoleCrawlJob,\n GENERATE_SCRIPT: processGenerateScriptJob,\n // The following job types are still processed in-process by the API.\n // If the worker claims one, the API crashed after creating it \u2014 mark failed so it's visible.\n GENERATE_TESTS: async (jobId: string, _config: any) => {\n logger.error({ jobId }, '[job-worker] GENERATE_TESTS claimed by worker \u2014 API did not process it in-process');\n await prismaAdmin.platformJob.update({\n where: { id: jobId },\n data: { status: 'FAILED', error: 'Test generation was not completed by the API \u2014 please retry from the UI.', finishedAt: new Date() },\n }).catch(() => { });\n },\n GENERATE_DATA: async (jobId: string, _config: any) => {\n logger.error({ jobId }, '[job-worker] GENERATE_DATA claimed by worker \u2014 API did not process it in-process');\n await prismaAdmin.platformJob.update({\n where: { id: jobId },\n data: { status: 'FAILED', error: 'Data generation was not completed by the API \u2014 please retry from the UI.', finishedAt: new Date() },\n }).catch(() => { });\n },\n GENERATE_TEST_PLAN: async (jobId: string, _config: any) => {\n logger.error({ jobId }, '[job-worker] GENERATE_TEST_PLAN claimed by worker \u2014 API did not process it in-process');\n await prismaAdmin.platformJob.update({\n where: { id: jobId },\n data: { status: 'FAILED', error: 'Test plan generation was not completed by the API \u2014 please retry from the UI.', finishedAt: new Date() },\n }).catch(() => { });\n },\n};\n\nasync function processRepoScanJob(jobId: string, config: any) {\n const { repoConnectionId, tenantId, projectId } = config;\n const jobStart = new Date();\n try {\n await updateJobProgress(tenantId, jobId, 5, 'Starting repo scan');\n\n const {\n fetchRepoTree, fetchFileContent, detectFramework,\n extractTestIds, extractStyleProfile,\n buildPageObjectInventory, resolveFixtureAliasesFromImports,\n } = await import('@detiq/agents');\n\n const conn = await withTenant(tenantId, (tx) =>\n tx.gitHubRepoConnection.findFirst({ where: { id: repoConnectionId, tenantId } })\n ) as any;\n if (!conn) throw new Error(`RepoConnection ${repoConnectionId} not found`);\n\n // Decrypt token\n const { decrypt } = await import('@detiq/core');\n const token = decrypt(conn.tokenEnc);\n\n // Mark scanning\n await withTenant(tenantId, (tx) =>\n tx.gitHubRepoConnection.update({ where: { id: repoConnectionId }, data: { scanStatus: 'SCANNING', scanError: null } })\n );\n\n await updateJobProgress(tenantId, jobId, 15, 'Fetching repo tree');\n // Use HEAD so it always resolves to the actual default branch regardless of stored value\n const treeResult = await fetchRepoTree(token, conn.repoFullName, 'HEAD');\n if (treeResult.truncated) {\n console.warn(`[repo-scan] ${conn.repoFullName}: tree truncated (>100k blobs) \u2014 detection may be incomplete`);\n await withTenant(tenantId, (tx) =>\n tx.gitHubRepoConnection.update({\n where: { id: repoConnectionId },\n data: { scanError: 'Repository tree was truncated by GitHub (>100k files) \u2014 some test files in deep subdirectories may be missing from this scan.' },\n })\n );\n }\n\n const frameworks = detectFramework(treeResult.paths);\n const primary = frameworks[0];\n await updateJobProgress(tenantId, jobId, 30, `Detected framework: ${primary?.framework ?? 'unknown'}`);\n\n // Prefer framework-detected glob when stored glob is the TS default (never explicitly set)\n const storedGlob = conn.testGlob || '';\n const testGlob = (storedGlob && storedGlob !== '**/*.spec.ts')\n ? storedGlob\n : (primary?.testGlob || storedGlob || '**/*.spec.ts');\n const framework = primary?.framework ?? 'unknown';\n const language = primary?.language ?? 'typescript';\n\n // Filter test files\n const { minimatch } = await import('minimatch').catch(() => ({ minimatch: null }));\n const isPythonTestFile = (p: string) =>\n p.endsWith('.py') && !p.endsWith('__init__.py') && !p.endsWith('conftest.py') &&\n (p.endsWith('_test.py') || /\\/test_[^/]+\\.py$/.test(p) || /^test_[^/]+\\.py$/.test(p) ||\n p.includes('/tests/') || p.includes('/test/'));\n // Java: testGlob matches all *.java \u2014 further filter to actual test classes only\n // (excludes helpers, base classes, utilities that don't follow *Test.java/*Tests.java naming)\n const isJavaTestFile = (p: string) =>\n p.endsWith('.java') && (\n /[A-Z]\\w*Tests?\\.java$/.test(p) ||\n (/\\/tests?\\//.test(p) && /[A-Z][a-z]/.test(p.split('/').pop() ?? ''))\n );\n // Build list of globs to try \u2014 primary + common variants so we don't miss .test.ts alongside .spec.ts\n const testGlobs = minimatch\n ? Array.from(new Set([\n testGlob,\n ...(testGlob === '**/*.spec.ts' ? ['**/*.test.ts', '**/*.cy.ts'] : []),\n ...(testGlob === '**/*.spec.js' ? ['**/*.test.js', '**/*.cy.js'] : []),\n ]))\n : [];\n const testPaths = treeResult.paths.filter((p) => {\n if (language === 'java') return isJavaTestFile(p);\n if (language === 'python') return isPythonTestFile(p);\n if (testGlobs.length) return testGlobs.some(g => (minimatch as any)(p, g, { matchBase: true }));\n return p.endsWith('.spec.ts') || p.endsWith('.spec.js') || p.endsWith('.test.ts') || p.endsWith('.test.js') || p.endsWith('.cy.ts') || p.endsWith('.cy.js') || p.endsWith('_test.py') || p.includes('/test_');\n }).slice(0, 2000); // cap at 2000 test files\n\n await updateJobProgress(tenantId, jobId, 40, `Found ${testPaths.length} test files`);\n\n if (testPaths.length === 0) {\n logger.warn({ jobId, repoConnectionId, framework }, '[repo-scan] no test files matched glob \u2014 scan complete with 0 results');\n await withTenant(tenantId, (tx) =>\n tx.gitHubRepoConnection.update({\n where: { id: repoConnectionId },\n data: { scanStatus: 'DONE', testFileCount: 0, extractedIdCount: 0, lastScannedAt: new Date(), lastSyncAt: new Date() },\n })\n );\n await updateJobProgress(tenantId, jobId, 100, 'Done: 0 test files found');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', finishedAt: new Date(), result: { testFileCount: 0, extractedIdCount: 0 } as any } })\n );\n return;\n }\n\n // Extract test IDs from each file\n const allExtracted: any[] = [];\n const styleSampleFiles: Array<{ path: string; content: string }> = [];\n let processed = 0;\n\n // Delete old test files for this connection\n await withTenant(tenantId, (tx) => tx.repoTestFile.deleteMany({ where: { repoConnectionId } }));\n\n for (const filePath of testPaths) {\n const content = await fetchFileContent(token, conn.repoFullName, filePath);\n if (!content) continue;\n\n // G-07: multi-file fixture tracing \u2014 resolve non-obvious aliases before extraction\n const extraAliases = await resolveFixtureAliasesFromImports(\n content, token, conn.repoFullName, filePath,\n ).catch(() => new Set<string>());\n const extracted = extractTestIds(content, filePath, framework, extraAliases);\n allExtracted.push(...extracted);\n\n await withTenant(tenantId, (tx) =>\n tx.repoTestFile.create({\n data: {\n tenantId,\n projectId,\n repoConnectionId,\n filePath,\n framework,\n extractedIds: extracted.map((e) => ({ name: e.name, normalizedName: e.normalizedName, lineNumber: e.lineNumber })) as any,\n rawSample: content.slice(0, 2000),\n lastScannedAt: new Date(),\n },\n })\n );\n\n if (styleSampleFiles.length < 5) styleSampleFiles.push({ path: filePath, content: content.slice(0, 3000) });\n processed++;\n\n if (processed % 10 === 0) {\n await updateJobProgress(tenantId, jobId, 40 + Math.floor((processed / testPaths.length) * 40), `Processed ${processed}/${testPaths.length} files`);\n }\n }\n\n // Fetch and parse framework config files (playwright.config.ts, cypress.config.ts, tsconfig.json)\n // Fetch and parse framework config files for all supported frameworks.\n // Pure regex/JSON \u2014 no LLM. Results override LLM inference for path detection.\n // Covers: playwright, cypress, jest, pytest, junit/maven/gradle, selenium-java/py,\n // webdriverio, cucumber \u2014 all frameworks in DetectedFramework.\n const configPaths: {\n testDir?: string; fixturesFolder?: string; baseURL?: string;\n tsconfigPaths?: Record<string, string[]>; tsconfigBaseUrl?: string;\n javaTestSourceDir?: string; jestModuleNameMapper?: Record<string, string>;\n } = {};\n try {\n const pathSet = new Set(treeResult.paths as string[]);\n const lang = framework.includes('java') ? 'java'\n : (framework.includes('py') || framework === 'pytest') ? 'python' : 'js';\n\n const configFilesToFetch: Array<{ path: string; type: string; dirPrefix: string }> = [];\n const tryAdd = (candidates: string[], type: string) => {\n for (const c of candidates) { if (pathSet.has(c)) { configFilesToFetch.push({ path: c, type, dirPrefix: '' }); break; } }\n };\n // Monorepo fallback: when root-level config is absent, find shallowest match anywhere in tree.\n // The dirPrefix (e.g. 'packages/e2e/') is prepended to testDir so paths are repo-root-relative.\n const scanTree = (pattern: RegExp, type: string) => {\n if (configFilesToFetch.some(f => f.type === type)) return;\n const all = (treeResult.paths as string[]).filter(p => pattern.test(p)).sort((a, b) => a.split('/').length - b.split('/').length);\n if (all[0]) {\n const lastSlash = all[0].lastIndexOf('/');\n const dirPrefix = lastSlash > 0 ? all[0].slice(0, lastSlash + 1) : '';\n configFilesToFetch.push({ path: all[0], type, dirPrefix });\n }\n };\n\n if (lang === 'js') {\n if (framework.startsWith('playwright')) {\n tryAdd(['playwright.config.ts', 'playwright.config.js'], 'playwright');\n scanTree(/(?:^|\\/)playwright\\.config\\.(ts|js|mts)$/, 'playwright');\n }\n if (framework.startsWith('cypress')) {\n tryAdd(['cypress.config.ts', 'cypress.config.js'], 'cypress');\n scanTree(/(?:^|\\/)cypress\\.config\\.(ts|js)$/, 'cypress');\n }\n if (framework === 'jest-ts' || framework === 'jest-js') {\n tryAdd(['jest.config.ts', 'jest.config.js', 'jest.config.mjs'], 'jest');\n scanTree(/(?:^|\\/)jest\\.config\\.(ts|js|mjs|cjs)$/, 'jest');\n tryAdd(['package.json'], 'package-json');\n }\n if (framework === 'webdriverio') {\n tryAdd(['wdio.conf.ts', 'wdio.conf.js', 'wdio.config.ts', 'wdio.config.js'], 'wdio');\n scanTree(/(?:^|\\/)wdio\\.(?:conf|config)\\.(ts|js|mjs)$/, 'wdio');\n }\n if (framework === 'cucumber' || framework === 'gherkin') {\n tryAdd(['.cucumber.js', '.cucumber.cjs', 'cucumber.json', '.cucumber.yaml', '.cucumber.yml'], 'cucumber');\n tryAdd(['package.json'], 'package-json');\n }\n tryAdd(['tsconfig.json'], 'tsconfig');\n scanTree(/(?:^|\\/)tsconfig(?:\\.base)?\\.json$/, 'tsconfig');\n } else if (lang === 'python') {\n tryAdd(['pytest.ini'], 'pytest-ini');\n scanTree(/(?:^|\\/)pytest\\.ini$/, 'pytest-ini');\n tryAdd(['pyproject.toml'], 'pyproject-toml');\n scanTree(/(?:^|\\/)pyproject\\.toml$/, 'pyproject-toml');\n tryAdd(['setup.cfg'], 'setup-cfg');\n } else if (lang === 'java') {\n tryAdd(['pom.xml'], 'pom-xml');\n scanTree(/(?:^|\\/)pom\\.xml$/, 'pom-xml');\n tryAdd(['build.gradle.kts', 'build.gradle'], 'build-gradle');\n scanTree(/(?:^|\\/)build\\.gradle(?:\\.kts)?$/, 'build-gradle');\n tryAdd(['testng.xml', 'src/test/resources/testng.xml'], 'testng-xml');\n }\n\n // Inline parsers \u2014 mirror parseFrameworkConfig from @detiq/agents (kept inline to avoid build dep)\n const normDir = (s: string) => s.replace(/^\\.\\//, '').replace(/\\/$/, '');\n const globRoot = (pattern: string): string | null => {\n const root = pattern.replace(/\\/\\*\\*.*$/, '').replace(/\\/\\*[^*].*$/, '');\n return (root && !root.startsWith('*') && !root.startsWith('{')) ? normDir(root) : null;\n };\n\n const fetched = await Promise.all(\n configFilesToFetch.map(async ({ path: cfgPath, type, dirPrefix }) => {\n const content = await fetchFileContent(token, conn.repoFullName, cfgPath).catch(() => null);\n if (!content) return null;\n const r: typeof configPaths = {};\n\n if (type === 'playwright') {\n const m = content.match(/\\btestDir\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (m) r.testDir = normDir(m[1]);\n const bm = content.match(/\\bbaseURL\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (bm) r.baseURL = bm[1];\n } else if (type === 'cypress') {\n const sm = content.match(/\\bspecPattern\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (sm) { const gr = globRoot(sm[1]); if (gr) r.testDir = gr; }\n const fm = content.match(/\\bfixturesFolder\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (fm) r.fixturesFolder = normDir(fm[1]);\n const bm = content.match(/\\bbaseUrl\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (bm) r.baseURL = bm[1];\n } else if (type === 'jest') {\n const rm = content.match(/\\broots\\s*:\\s*\\[([^\\]]+)\\]/);\n if (rm) { const fr = rm[1].match(/['\"`]([^'\"`]+)['\"`]/)?.[1]; if (fr) r.testDir = fr.replace(/<rootDir>\\/?/, '').replace(/\\/$/, '') || 'src'; }\n if (!r.testDir) { const tm = content.match(/\\btestMatch\\s*:\\s*\\[([^\\]]+)\\]/); if (tm) { const fp = tm[1].match(/['\"`]([^'\"`]+)['\"`]/)?.[1]; if (fp) { const gr = globRoot(fp.replace(/<rootDir>\\/?/, '')); if (gr) r.testDir = gr; } } }\n const mm = content.match(/\\bmoduleNameMapper\\s*:\\s*\\{([^}]+)\\}/s);\n if (mm) { const map: Record<string, string> = {}; for (const [, k, v] of mm[1].matchAll(/['\"`]([^'\"`]+)['\"`]\\s*:\\s*['\"`]([^'\"`]+)['\"`]/g)) map[k] = v; if (Object.keys(map).length) r.jestModuleNameMapper = map; }\n } else if (type === 'package-json') {\n try { const p = JSON.parse(content) as any; const j = p?.jest ?? {}; if (Array.isArray(j.roots) && j.roots[0]) r.testDir = String(j.roots[0]).replace(/<rootDir>\\/?/, '').replace(/\\/$/, '') || 'src'; if (!r.testDir && Array.isArray(j.testMatch) && j.testMatch[0]) { const gr = globRoot(String(j.testMatch[0]).replace(/<rootDir>\\/?/, '')); if (gr) r.testDir = gr; } if (j.moduleNameMapper && typeof j.moduleNameMapper === 'object') r.jestModuleNameMapper = j.moduleNameMapper; } catch { /* ignore */ }\n } else if (type === 'tsconfig') {\n try { const stripped = content.replace(/\\/\\/[^\\n]*/g, '').replace(/,(\\s*[}\\]])/g, '$1'); const p = JSON.parse(stripped) as any; const opts = p?.compilerOptions ?? {}; if (opts.paths && typeof opts.paths === 'object') r.tsconfigPaths = opts.paths; if (typeof opts.baseUrl === 'string') r.tsconfigBaseUrl = opts.baseUrl; } catch { /* ignore */ }\n } else if (type === 'pytest-ini') {\n const m = content.match(/^\\s*testpaths\\s*=\\s*(.+)$/m);\n if (m) { const first = m[1].trim().split(/\\s+/)[0]; if (first) r.testDir = normDir(first); }\n } else if (type === 'pyproject-toml') {\n const m = content.match(/testpaths\\s*=\\s*\\[([^\\]]+)\\]/);\n if (m) { const first = m[1].match(/['\"]([^'\"]+)['\"]/)?.[1]; if (first) r.testDir = normDir(first); }\n if (!r.testDir) { const m2 = content.match(/testpaths\\s*=\\s*['\"]([^'\"]+)['\"]/); if (m2) r.testDir = normDir(m2[1]); }\n } else if (type === 'setup-cfg') {\n const m = content.match(/^\\s*testpaths\\s*=\\s*(.+)$/m);\n if (m) { const first = m[1].trim().split(/[\\s,]+/)[0]; if (first) r.testDir = normDir(first); }\n } else if (type === 'pom-xml') {\n const m = content.match(/<testSourceDirectory>\\s*([^<]+)\\s*<\\/testSourceDirectory>/);\n if (m) r.javaTestSourceDir = normDir(m[1].trim());\n } else if (type === 'build-gradle') {\n const m = content.match(/srcDir[s]?\\s*[=([]?\\s*['\"`]([^'\"`]+)['\"`]/) ?? content.match(/srcDir\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/);\n if (m) r.javaTestSourceDir = normDir(m[1]);\n } else if (type === 'wdio') {\n const sm = content.match(/\\bspecs\\s*:\\s*\\[([^\\]]+)\\]/);\n if (sm) { const fp = sm[1].match(/['\"`]([^'\"`]+)['\"`]/)?.[1]; if (fp) { const gr = globRoot(fp); if (gr) r.testDir = gr; } }\n const bm = content.match(/\\bbaseUrl\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (bm) r.baseURL = bm[1];\n } else if (type === 'cucumber') {\n const pm = content.match(/\\bpaths\\s*:\\s*\\[([^\\]]+)\\]/);\n if (pm) { const fp = pm[1].match(/['\"`]([^'\"`]+)['\"`]/)?.[1]; if (fp) { const gr = globRoot(fp); if (gr) r.testDir = gr; } }\n if (!r.testDir) { try { const p = JSON.parse(content) as any; const paths = p?.default?.paths ?? p?.paths; if (Array.isArray(paths) && paths[0]) { const gr = globRoot(String(paths[0])); if (gr) r.testDir = gr; } } catch { /* not JSON */ } }\n }\n // Monorepo workspace prefix: config found at e.g. packages/frontend/playwright.config.ts.\n // testDir from config is relative to that file ('tests' or 'e2e'). Prepend the directory\n // so the final path is repo-root-relative ('packages/frontend/tests').\n if (dirPrefix && r.testDir && !r.testDir.startsWith(dirPrefix)) {\n r.testDir = dirPrefix + r.testDir;\n }\n if (dirPrefix && r.javaTestSourceDir && !r.javaTestSourceDir.startsWith(dirPrefix)) {\n r.javaTestSourceDir = dirPrefix + r.javaTestSourceDir;\n }\n return r;\n })\n );\n\n for (const parsed of fetched) {\n if (!parsed) continue;\n if (parsed.testDir && !configPaths.testDir) configPaths.testDir = parsed.testDir;\n if (parsed.fixturesFolder && !configPaths.fixturesFolder) configPaths.fixturesFolder = parsed.fixturesFolder;\n if (parsed.baseURL && !configPaths.baseURL) configPaths.baseURL = parsed.baseURL;\n if (parsed.tsconfigPaths && !configPaths.tsconfigPaths) configPaths.tsconfigPaths = parsed.tsconfigPaths;\n if (parsed.tsconfigBaseUrl && !configPaths.tsconfigBaseUrl) configPaths.tsconfigBaseUrl = parsed.tsconfigBaseUrl;\n if (parsed.javaTestSourceDir && !configPaths.javaTestSourceDir) configPaths.javaTestSourceDir = parsed.javaTestSourceDir;\n if (parsed.jestModuleNameMapper && !configPaths.jestModuleNameMapper) configPaths.jestModuleNameMapper = parsed.jestModuleNameMapper;\n }\n } catch { /* non-fatal */ }\n\n // Extract style profile for aligned generation\n // For Java, include main source paths so LLM can detect page object location in src/main/java/\n // Fall back to existing styleProfile from DB if extraction fails or returns null.\n let styleProfile: any = conn.styleProfile ?? null;\n if (styleSampleFiles.length > 0) {\n try {\n const mainSourcePaths = language === 'java'\n ? treeResult.paths.filter((p: string) => /^src\\/main\\/java\\//.test(p)).slice(0, 50)\n : [];\n // For TS/JS, also include non-test source files so LLM can detect pageObjectLocation\n // (e.g. src/advantage/pages/ in playwright-sample-project)\n const testPathSet = new Set(testPaths);\n const sourceFilePaths = (language === 'typescript' || language === 'javascript')\n ? treeResult.paths\n .filter((p: string) =>\n (p.endsWith('.ts') || p.endsWith('.js')) &&\n !testPathSet.has(p) &&\n !p.endsWith('.d.ts') &&\n !p.includes('node_modules/') &&\n !p.includes('dist/') &&\n !p.includes('.config.'))\n .slice(0, 60)\n : [];\n const allPathsForProfile = [...testPaths, ...mainSourcePaths, ...sourceFilePaths].slice(0, 200);\n // Pass full tree paths separately so detectDataDirs can find testdata/*.json, fixtures/*.yaml etc.\n const freshProfile = await extractStyleProfile(styleSampleFiles, framework, language, tenantId, projectId, jobId, allPathsForProfile, treeResult.paths);\n if (freshProfile) styleProfile = freshProfile;\n } catch { /* non-fatal */ }\n }\n\n // Merge config file ground-truth into styleProfile (Priority 0 \u2014 highest, no LLM inference needed).\n // Java: javaTestSourceDir also sets testLocation (equivalent field for Java).\n if (styleProfile && Object.keys(configPaths).length > 0) {\n if (configPaths.testDir) styleProfile.configTestDir = configPaths.testDir;\n if (configPaths.javaTestSourceDir) {\n styleProfile.configTestDir = configPaths.javaTestSourceDir;\n // Also set testLocation if not already present from LLM scan\n if (!styleProfile.testLocation) styleProfile.testLocation = configPaths.javaTestSourceDir;\n }\n if (configPaths.fixturesFolder) styleProfile.configFixturesFolder = configPaths.fixturesFolder;\n if (configPaths.baseURL) styleProfile.configBaseURL = configPaths.baseURL;\n if (configPaths.tsconfigPaths) styleProfile.tsconfigPaths = configPaths.tsconfigPaths;\n if (configPaths.tsconfigBaseUrl) styleProfile.tsconfigBaseUrl = configPaths.tsconfigBaseUrl;\n if (configPaths.jestModuleNameMapper) styleProfile.jestModuleNameMapper = configPaths.jestModuleNameMapper;\n }\n\n // Extract page object inventory \u2014 finds POM/page classes in non-test files\n // Fall back to existing inventory from DB if fetch fails.\n let pageObjectInventory: any[] = (conn.pageObjectInventory as any[]) ?? [];\n try {\n const freshInventory = await buildPageObjectInventory(token, conn.repoFullName, treeResult.paths, framework);\n if (freshInventory.length > 0) {\n pageObjectInventory = freshInventory;\n await updateJobProgress(tenantId, jobId, 82, `Found ${pageObjectInventory.length} page objects`);\n }\n } catch { /* non-fatal */ }\n\n // Run coverage mapping\n await updateJobProgress(tenantId, jobId, 85, 'Running coverage mapping');\n const { buildCoverageMap } = await import('@detiq/agents');\n const testCases = await withTenant(tenantId, (tx) =>\n tx.testCase.findMany({ where: { projectId, tenantId, status: { not: 'RETIRED' } }, select: { id: true, title: true, ref: true } })\n ) as any[];\n\n const mappings = await buildCoverageMap(testCases, allExtracted, tenantId);\n\n // Upsert CoverageMapping records (skip manualOverride=true)\n for (const m of mappings) {\n const existing = await withTenant(tenantId, (tx) =>\n tx.coverageMapping.findUnique({ where: { testCaseId: m.testCaseId } })\n ) as any;\n if (existing?.manualOverride) continue;\n\n await withTenant(tenantId, (tx) =>\n tx.coverageMapping.upsert({\n where: { testCaseId: m.testCaseId },\n create: { tenantId, projectId, testCaseId: m.testCaseId, repoConnectionId, status: m.status, matchedFilePath: m.matchedFilePath ?? null, scriptTestName: m.scriptTestName ?? null, lineNumber: m.lineNumber ?? null, confidence: m.confidence, matchMethod: m.matchMethod },\n update: { status: m.status, matchedFilePath: m.matchedFilePath ?? null, scriptTestName: m.scriptTestName ?? null, lineNumber: m.lineNumber ?? null, confidence: m.confidence, matchMethod: m.matchMethod },\n })\n );\n }\n\n const mapped = mappings.filter((m) => m.status === 'MAPPED').length;\n const unmapped = mappings.filter((m) => m.status === 'UNMAPPED').length;\n const ambiguous = mappings.filter((m) => m.status === 'AMBIGUOUS').length;\n const coveragePercent = testCases.length > 0 ? (mapped / testCases.length) * 100 : 0;\n\n // Write coverage snapshot\n await withTenant(tenantId, (tx) =>\n tx.coverageSnapshot.create({\n data: { tenantId, projectId, totalCases: testCases.length, mappedCases: mapped, unmappedCases: unmapped, ambiguousCases: ambiguous, coveragePercent },\n })\n );\n\n // For Java, if styleProfile still missing testStructurePattern, infer two-tier from tree paths.\n // This covers the case where both extractStyleProfile and buildPageObjectInventory failed.\n const resolvedStyleProfile: any = (() => {\n const sp = styleProfile ?? {};\n if (framework === 'selenium-java' && !sp.testStructurePattern) {\n const hasMavenMain = treeResult.paths.some((p: string) => /^src\\/main\\/java\\//.test(p));\n if (hasMavenMain) return { ...sp, testStructurePattern: 'two-tier' };\n }\n return sp;\n })();\n\n // Update repo connection with results\n await withTenant(tenantId, (tx) =>\n tx.gitHubRepoConnection.update({\n where: { id: repoConnectionId },\n data: {\n scanStatus: 'DONE',\n detectedFramework: framework,\n configFilePath: primary?.configPath ?? null,\n testGlob: testGlob,\n testFileCount: testPaths.length,\n extractedIdCount: allExtracted.length,\n styleProfile: resolvedStyleProfile as any,\n pageObjectInventory: pageObjectInventory.length > 0 ? pageObjectInventory as any : undefined,\n treeSnapshot: JSON.stringify(treeResult.paths).slice(0, 100000),\n lastScannedAt: new Date(),\n lastSyncAt: new Date(),\n },\n })\n );\n try {\n const { generateRepoSkillFiles } = await import('@detiq/agents');\n await generateRepoSkillFiles(tenantId, projectId, {\n repoFullName: conn.repoFullName,\n framework,\n language,\n testGlob,\n styleProfile: resolvedStyleProfile,\n pageObjectInventory,\n testPaths,\n extractedTestCount: allExtracted.length,\n treeWasTruncated: treeResult.truncated,\n extractedTests: allExtracted,\n });\n } catch (e: any) {\n logger.warn({ jobId, repoConnectionId, err: e?.message }, '[repo-scan] repo skill files generation failed (non-fatal)');\n }\n try {\n const { alignSkillFile } = await import('@detiq/agents');\n await alignSkillFile(tenantId, projectId, {\n framework,\n language,\n styleProfile: resolvedStyleProfile,\n repoFullName: conn.repoFullName,\n });\n } catch (e: any) {\n logger.warn({ jobId, repoConnectionId, err: e?.message }, '[repo-scan] alignSkillFile failed (non-fatal)');\n }\n\n await updateJobProgress(tenantId, jobId, 100, `Done: ${mapped} mapped, ${unmapped} unmapped`);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', finishedAt: new Date(), result: { testFileCount: testPaths.length, extractedIdCount: allExtracted.length, mapped, unmapped, ambiguous, coveragePercent } as any } })\n );\n aggregateJobTokens(tenantId, jobId, projectId, jobStart).catch(console.error);\n } catch (err: any) {\n const cause = err?.cause?.message ?? (typeof err?.cause === 'string' ? err.cause : '');\n const msg = `${String(err?.message ?? err)}${cause ? ` \u2014 ${cause}` : ''}`;\n logger.error({ jobId, repoConnectionId, err: msg }, '[repo-scan] failed');\n await withTenant(tenantId, (tx) =>\n tx.gitHubRepoConnection.update({ where: { id: repoConnectionId }, data: { scanStatus: 'FAILED', scanError: msg.slice(0, 500) } })\n ).catch(() => { });\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', finishedAt: new Date(), error: msg.slice(0, 1000) } })\n ).catch(() => { });\n }\n}\n\n// \u2500\u2500 BATCH_GENERATE_ALIGNED \u2014 generate framework-aligned scripts for UNMAPPED cases \u2500\u2500\n\nasync function processBatchGenerateAlignedJob(jobId: string, config: any) {\n const { tenantId, projectId, repoConnectionId, scope = 'UNMAPPED', testCaseIds, force = false } = config;\n try {\n await updateJobProgress(tenantId, jobId, 5, 'Loading repo connection');\n\n const conn = await withTenant(tenantId, (tx) =>\n (tx as any).gitHubRepoConnection.findFirst({ where: { id: repoConnectionId, tenantId } })\n ) as any;\n if (!conn) throw new Error(`RepoConnection ${repoConnectionId} not found`);\n\n const { decrypt } = await import('@detiq/core');\n\n const {\n generateScriptAligned, deriveRepoTargetPath, validateGeneratedScript,\n mapWithConcurrency, SCRIPT_GEN_CONCURRENCY,\n } = await import('@detiq/agents');\n\n const styleProfile = conn.styleProfile as any;\n const pageObjectInventory = (conn.pageObjectInventory as any[]) ?? [];\n const framework = conn.detectedFramework ?? 'playwright-ts';\n const language = styleProfile?.language ?? 'typescript';\n\n // Resolve which test cases to process\n let caseIds: string[];\n if (testCaseIds && testCaseIds.length > 0) {\n caseIds = testCaseIds;\n } else if (scope === 'UNMAPPED') {\n const mappings = await withTenant(tenantId, (tx) =>\n (tx as any).coverageMapping.findMany({\n where: { tenantId, projectId, repoConnectionId, status: 'UNMAPPED' },\n select: { testCaseId: true },\n })\n ) as any[];\n caseIds = mappings.map((m: any) => m.testCaseId);\n } else {\n const cases = await withTenant(tenantId, (tx) =>\n tx.testCase.findMany({\n where: { tenantId, projectId, status: { not: 'RETIRED' } },\n select: { id: true },\n })\n ) as any[];\n caseIds = cases.map((c: any) => c.id);\n }\n\n await updateJobProgress(tenantId, jobId, 15, `Generating scripts for ${caseIds.length} test cases`);\n\n let done = 0;\n const errors: string[] = [];\n\n // One LLM call per case \u2014 bounded worker pool instead of a strictly serial loop,\n // which made large batches take an hour or more.\n await mapWithConcurrency(caseIds, SCRIPT_GEN_CONCURRENCY, async (tcId: string) => {\n try {\n // Skip if script already exists and not forcing\n if (!force) {\n const existing = await withTenant(tenantId, (tx) =>\n (tx as any).testScript.findFirst({ where: { testCaseId: tcId } })\n );\n if (existing) { done++; return; }\n }\n\n // Get screen URL and ref for target path derivation\n const tc = await withTenant(tenantId, (tx) =>\n tx.testCase.findUnique({\n where: { id: tcId },\n select: { id: true, ref: true, title: true, screen: { select: { url: true } } },\n })\n );\n\n const screenUrl = (tc as any)?.screen?.url ?? '';\n const repoTargetPath = styleProfile\n ? deriveRepoTargetPath(screenUrl, styleProfile, framework)\n : deriveFallbackTargetPath((tc as any)?.ref ?? (tc as any)?.title ?? tcId, framework);\n\n const result = await generateScriptAligned({\n tenantId, projectId, testCaseId: tcId,\n styleProfile,\n pageObjectInventory,\n repoTargetPath,\n save: true,\n });\n\n // Validate the generated script\n if (result.code) {\n const validation = await validateGeneratedScript(result.code, framework, language);\n await withTenant(tenantId, (tx) =>\n (tx as any).testScript.updateMany({\n where: { testCaseId: tcId },\n data: {\n validationStatus: validation.ok ? 'PASS' : 'FAIL',\n validationErrors: validation.ok ? null : validation.errors.slice(0, 2000),\n },\n })\n );\n }\n\n done++;\n } catch (e: any) {\n errors.push(`${tcId}: ${String(e.message).slice(0, 100)}`);\n }\n\n if (done % 5 === 0) {\n await updateJobProgress(tenantId, jobId, 15 + Math.floor((done / caseIds.length) * 80), `Generated ${done}/${caseIds.length}`);\n }\n });\n\n refreshTestScriptSkillFile(tenantId, projectId).catch(console.error);\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', finishedAt: new Date(), result: { done, errors: errors.slice(0, 20), total: caseIds.length } as any },\n })\n );\n } catch (err: any) {\n const msg = String(err?.message ?? err);\n logger.error({ jobId, err: msg }, '[batch-generate-aligned] failed');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', finishedAt: new Date(), error: msg.slice(0, 1000) } })\n ).catch(() => { });\n }\n}\n\n// \u2500\u2500 CREATE_PR \u2014 push generated scripts to GitHub as a PR \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function processCreatePRJob(jobId: string, config: any) {\n const { tenantId, projectId, repoConnectionId, testScriptIds, crawlJobId } = config;\n try {\n await updateJobProgress(tenantId, jobId, 5, 'Loading connection');\n\n const conn = await withTenant(tenantId, (tx) =>\n (tx as any).gitHubRepoConnection.findFirst({ where: { id: repoConnectionId, tenantId } })\n ) as any;\n if (!conn) throw new Error(`RepoConnection ${repoConnectionId} not found`);\n\n const { decrypt } = await import('@detiq/core');\n const token = decrypt(conn.tokenEnc);\n\n const { pushScriptsAsPR, getFileContent, mergeSpecContent } = await import('@detiq/agents');\n\n const framework = conn.detectedFramework ?? 'playwright-ts';\n\n // Collect scripts + their target paths\n let scripts: any[];\n if (testScriptIds && testScriptIds.length > 0) {\n scripts = await withTenant(tenantId, (tx) =>\n (tx as any).testScript.findMany({\n where: { id: { in: testScriptIds }, tenantId, code: { not: null } },\n include: { testCase: { select: { title: true, ref: true, repoTargetPath: true } } },\n })\n );\n } else {\n // All scripts that haven't been pushed yet\n scripts = await withTenant(tenantId, (tx) =>\n (tx as any).testScript.findMany({\n where: { tenantId, projectId, pushedToPR: false, validationStatus: { in: ['PASS', 'PENDING'] }, code: { not: null } },\n include: { testCase: { select: { title: true, ref: true, repoTargetPath: true } } },\n })\n );\n }\n\n if (scripts.length === 0) throw new Error('No generated scripts found to push.');\n\n // For each file, check if it already exists in the repo and merge if so\n const files = await Promise.all(scripts.map(async (s: any) => {\n const path = s.testCase?.repoTargetPath\n ?? deriveFallbackTargetPath(s.testCase?.ref ?? s.testCase?.title ?? s.id, framework);\n let content = s.code;\n\n // Spec files: attempt to merge with existing content\n const isSpecFile = /\\.(spec|test|cy)\\.(ts|js|py|java)$/.test(path) || /_test\\.py$/.test(path);\n if (isSpecFile) {\n const existing = await getFileContent(token, conn.repoFullName, path, conn.defaultBranch).catch(() => null);\n if (existing && existing.trim().length > 0) {\n logger.info({ path }, '[create-pr] merging new tests into existing spec file');\n content = mergeSpecContent(existing, s.code);\n }\n }\n\n return { path, content };\n }));\n\n if (files.length === 0) throw new Error('No scripts to push.');\n\n await updateJobProgress(tenantId, jobId, 20, `Pushing ${files.length} scripts to GitHub`);\n\n const now = new Date().toISOString().slice(0, 10);\n const branchName = `zeta/new-coverage-${now}-${jobId.slice(0, 8)}`;\n\n const prBody = buildPRBody(scripts, projectId);\n\n const prResult = await pushScriptsAsPR({\n token,\n repo: conn.repoFullName,\n baseBranch: conn.defaultBranch,\n branchName,\n files,\n prTitle: `ZeTa: Add test coverage for ${files.length} new scenario${files.length === 1 ? '' : 's'}`,\n prBody,\n draft: false,\n });\n\n // Record the PR\n const pr = await withTenant(tenantId, (tx) =>\n (tx as any).gitHubPR.create({\n data: {\n tenantId, projectId,\n repoConnectionId,\n prNumber: prResult.prNumber,\n prUrl: prResult.prUrl,\n branch: prResult.branch,\n headSha: prResult.headSha,\n status: 'OPEN',\n crawlJobId: crawlJobId ?? null,\n scriptCount: files.length,\n prBody: prBody.slice(0, 10000),\n },\n })\n ) as any;\n\n // Mark scripts as pushed\n const scriptIds = scripts.map((s: any) => s.id);\n await withTenant(tenantId, (tx) =>\n (tx as any).testScript.updateMany({\n where: { id: { in: scriptIds } },\n data: { pushedToPR: true, pushedPRId: pr.id },\n })\n );\n\n await notifyAdmins(tenantId, {\n type: 'GITHUB_PR_CREATED',\n title: 'ZeTa created a GitHub PR',\n body: `${files.length} new test script${files.length === 1 ? '' : 's'} pushed. PR #${prResult.prNumber}: ${prResult.prUrl}`,\n metadata: { prUrl: prResult.prUrl, prNumber: prResult.prNumber, scriptCount: files.length },\n });\n\n await updateJobProgress(tenantId, jobId, 100, `PR #${prResult.prNumber} created`);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', finishedAt: new Date(), result: { prUrl: prResult.prUrl, prNumber: prResult.prNumber, scriptCount: files.length } as any },\n })\n );\n } catch (err: any) {\n const msg = String(err?.message ?? err);\n logger.error({ jobId, err: msg }, '[create-pr] failed');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', finishedAt: new Date(), error: msg.slice(0, 1000) } })\n ).catch(() => { });\n }\n}\n\nfunction buildPRBody(scripts: any[], projectId: string): string {\n const lines = [\n '## ZeTa \u2014 Automated Test Coverage',\n '',\n `Generated by ZeTa Quality Intelligence Platform for project \\`${projectId}\\`.`,\n '',\n '### Scripts Added',\n '',\n '| Test Case | Target Path |',\n '|---|---|',\n ];\n for (const s of scripts.slice(0, 30)) {\n const title = s.testCase?.title ?? s.id;\n const path = s.testCase?.repoTargetPath ?? '(unknown)';\n lines.push(`| ${title.replace(/\\|/g, '\\\\|').slice(0, 60)} | \\`${path}\\` |`);\n }\n if (scripts.length > 30) lines.push(`| ... and ${scripts.length - 30} more | |`);\n lines.push('', '---', '_Review each test carefully before merging. ZeTa generates scripts aligned to your existing framework conventions._');\n return lines.join('\\n');\n}\n\n// \u2500\u2500 NEW_PAGES_PIPELINE \u2014 post-crawl: detect new screens, generate tests + scripts \u2500\u2500\n\nasync function processNewPagesPipelineJob(jobId: string, config: any) {\n const { tenantId, projectId, crawlJobId, repoConnectionId, autoPR = false } = config;\n try {\n await updateJobProgress(tenantId, jobId, 5, 'Detecting new screens');\n\n // Find repo connection (use provided or find first active)\n const conn = await withTenant(tenantId, (tx) =>\n (tx as any).gitHubRepoConnection.findFirst({\n where: repoConnectionId\n ? { id: repoConnectionId, tenantId }\n : { tenantId, projectId, status: 'ACTIVE' },\n })\n ) as any;\n\n // Get all screens last seen after the lastSyncAt of the repo connection\n const sinceDate = conn?.lastSyncAt ?? new Date(0);\n const newScreens = await withTenant(tenantId, (tx) =>\n tx.screen.findMany({\n where: {\n tenantId,\n projectId,\n lastSeenAt: { gt: sinceDate },\n status: { not: 'DELETED' },\n },\n select: { id: true, url: true, name: true },\n take: 100,\n })\n ) as any[];\n\n if (newScreens.length === 0) {\n await updateJobProgress(tenantId, jobId, 100, 'No new screens to process');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', finishedAt: new Date(), result: { newScreens: 0 } as any } })\n );\n return;\n }\n\n await updateJobProgress(tenantId, jobId, 15, `Found ${newScreens.length} new screens`);\n\n // For each new screen, enqueue GENERATE_TESTS if no test cases exist\n const generationJobs: string[] = [];\n for (const screen of newScreens) {\n const caseCount = await withTenant(tenantId, (tx) =>\n tx.testCase.count({ where: { tenantId, screenId: screen.id } })\n );\n if (caseCount === 0) {\n const jid = await enqueueJob('GENERATE_SCREEN_TESTS', {\n tenantId, projectId, screenId: screen.id,\n triggerAlignedScripts: conn != null,\n repoConnectionId: (conn as any)?.id ?? null,\n });\n generationJobs.push(jid);\n }\n }\n\n await updateJobProgress(tenantId, jobId, 60, `Queued ${generationJobs.length} test generation jobs`);\n\n // If repo connected and autoPR enabled, schedule BATCH_GENERATE_ALIGNED after generation\n if (conn && autoPR) {\n await enqueueJob('BATCH_GENERATE_ALIGNED', {\n tenantId, projectId,\n repoConnectionId: conn.id,\n scope: 'UNMAPPED',\n });\n }\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', finishedAt: new Date(), result: { newScreens: newScreens.length, generationJobs: generationJobs.length } as any },\n })\n );\n } catch (err: any) {\n const msg = String(err?.message ?? err);\n logger.error({ jobId, err: msg }, '[new-pages-pipeline] failed');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', finishedAt: new Date(), error: msg.slice(0, 1000) } })\n ).catch(() => { });\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * ZeTa-AI Background Job Queue\n *\n * Postgres-backed durable queue. Each job becomes a PlatformJob record.\n * enqueueJob() writes it QUEUED and, for same-process responsiveness, kicks\n * an immediate best-effort claim attempt \u2014 but durability comes from\n * startJobWorkerLoop()'s poll: it atomically claims QUEUED jobs (Postgres\n * `FOR UPDATE SKIP LOCKED`, safe across any number of concurrent worker\n * processes/containers) and requeues jobs whose heartbeat went stale,\n * so a crashed/restarted worker never silently loses a job.\n *\n * CRAWL-type jobs additionally support AWS SQS as an alternate transport\n * (JOB_QUEUE_BACKEND=sqs, see sqs-queue.ts) for horizontally-scaled crawler\n * fleets; every other job type always goes through the DB-backed queue.\n */\n\nimport crypto from 'node:crypto';\nimport { withTenant, prismaAdmin } from '@detiq/database';\nimport { crawlJobsTotal, crawlJobDurationSeconds } from '@detiq/core';\nimport { logger } from './logger.js';\nimport { AppBrain, getProjectStorageConfig, buildArtifactKey, uploadArtifact, getTenantStorageConfig, uploadScreenshotToTenantStorage, uploadFileToTenantStorage, notifyRunCompletion, recordLLMCall, downloadBufferFromTenantStorage, dispatchToTenantAdmins } from '@detiq/app-brain';\n\nasync function notifyAdmins(tenantId: string, data: {\n type: string; title: string; body: string; metadata?: Record<string, unknown>\n}) {\n try {\n await dispatchToTenantAdmins(tenantId, data as any);\n } catch (e) {\n console.error('[notify] failed:', e);\n }\n}\nimport { generateTaxonomySkillFiles, groupScreensWithAI, generateExecutionPlan, refreshTestCasesSkillFile, refreshTestDataSkillFile, refreshTestScriptSkillFile, refreshRequirementsCoverageSkillFile, refreshTestExecutionHistorySkillFile, refreshVisualRegressionHistorySkillFile, refreshLocatorStrategyWithHealingData, refreshFieldCatalogSkillFile, detectFieldsFromHtml, mergeIntoCatalog, generateArtifactIntelligence } from '@detiq/agents';\nimport type { ExecutionStep } from '@detiq/agents';\nimport { crawlProject, domFallbackElements } from './crawler.js';\nimport { httpCrawl } from './http-engine.js';\nimport { hashContent, normalizeText } from './incremental-diff.js';\nimport { discoverProjectUrls } from './sitemap.js';\nimport { executeExecutionStep, executeTestPlan } from './test-executor.js';\nimport { runNativeCrawl } from './native-crawler.js';\nimport { elementsToPlaywrightLocators } from './locator-extractor.js';\nimport { jira, confluence, figma } from '@detiq/connectors';\nimport { decrypt, config as platformConfig, dispatchWebhook, isFeatureEnabled, interpolatePlaceholders } from '@detiq/core';\nimport { createHmac } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport os from 'node:os';\n\n// \u2500\u2500 GAP-14/15: Structured failure classification with per-type backoff \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport enum FailureType {\n TRANSIENT = 'TRANSIENT', // network timeout, rate limit, 429, 503\n INFRASTRUCTURE = 'INFRASTRUCTURE', // DB connection, OOM, worker crash\n LOGICAL = 'LOGICAL', // bad input, business rule violation, 400/422\n FATAL = 'FATAL', // unrecoverable \u2014 bad config, missing secrets\n}\n\ninterface BackoffConfig {\n maxRetries: number;\n baseDelayMs: number;\n multiplier: number;\n maxDelayMs: number;\n}\n\nconst BACKOFF_BY_TYPE: Record<FailureType, BackoffConfig> = {\n [FailureType.TRANSIENT]: { maxRetries: 5, baseDelayMs: 30_000, multiplier: 2, maxDelayMs: 300_000 },\n [FailureType.INFRASTRUCTURE]: { maxRetries: 3, baseDelayMs: 60_000, multiplier: 3, maxDelayMs: 600_000 },\n [FailureType.LOGICAL]: { maxRetries: 1, baseDelayMs: 120_000, multiplier: 1, maxDelayMs: 120_000 },\n [FailureType.FATAL]: { maxRetries: 0, baseDelayMs: 0, multiplier: 1, maxDelayMs: 0 },\n};\n\nexport function classifyFailure(error: string | Error | unknown): FailureType {\n const msg = (error instanceof Error ? error.message : String(error)).toLowerCase();\n\n // Fatal \u2014 bad configuration, missing secrets\n if (msg.includes('master_enc_key') || msg.includes('missing secret') || msg.includes('invalid configuration')) {\n return FailureType.FATAL;\n }\n\n // Infrastructure \u2014 OOM, DB, worker crashes\n if (\n msg.includes('out of memory') || msg.includes('heap out') ||\n msg.includes('connection refused') || msg.includes('econnrefused') ||\n msg.includes('prisma') || msg.includes('database') ||\n msg.includes('worker exited') || msg.includes('sigkill')\n ) {\n return FailureType.INFRASTRUCTURE;\n }\n\n // Logical \u2014 validation, auth, bad input\n if (\n msg.includes('validation') || msg.includes('invalid input') ||\n msg.includes('not found') || msg.includes('unauthorized') ||\n msg.includes('bad request') || msg.includes('400') || msg.includes('422') ||\n msg.includes('403')\n ) {\n return FailureType.LOGICAL;\n }\n\n // Default: TRANSIENT (network, timeout, rate limit, 429, 503)\n return FailureType.TRANSIENT;\n}\n\nfunction computeNextRetryAt(failureType: FailureType, retryCount: number): Date | null {\n const config = BACKOFF_BY_TYPE[failureType];\n if (retryCount >= config.maxRetries) return null; // exceeded max \u2014 go to DLQ\n const delayMs = Math.min(config.baseDelayMs * Math.pow(config.multiplier, retryCount), config.maxDelayMs);\n return new Date(Date.now() + delayMs);\n}\n\n/**\n * Centralized failure recorder for all PlatformJobs.\n * - Classifies error into FailureType (TRANSIENT, INFRASTRUCTURE, LOGICAL, FATAL)\n * - Computes exponential backoff per failure classification\n * - If FATAL or retries exhausted: marks status='FAILED', dlq=true, dlqAt=NOW(), dlqReason=error\n * - If retryable: marks status='RETRY_WAIT', nextRetryAt=NOW() + backoff, retryCount=retryCount+1\n * - Persists checkpoint and checkpointAt if checkpointData provided\n * - Updates failureType on the row and in result.failureType\n */\nexport async function recordJobFailure(\n tenantId: string | null | undefined,\n jobId: string,\n err: unknown,\n checkpointData?: any,\n options?: { isAborted?: boolean }\n): Promise<void> {\n const errorMessage = err instanceof Error ? err.message : String(err);\n const aborted = options?.isAborted ?? (errorMessage.includes('CRAWL_ABORTED') || errorMessage.includes('Stopped by user'));\n\n if (aborted) {\n await prismaAdmin.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'CANCELLED',\n error: 'Stopped by user',\n finishedAt: new Date(),\n heartbeatAt: null,\n workerId: null,\n ...(checkpointData ? { checkpoint: checkpointData, checkpointAt: new Date() } : {}),\n },\n }).catch(() => {});\n return;\n }\n\n const failureType = classifyFailure(err);\n const jobRow = await prismaAdmin.platformJob.findUnique({\n where: { id: jobId },\n select: { retryCount: true, result: true }\n }).catch(() => null);\n\n const currentRetry = jobRow?.retryCount ?? 0;\n const nextRetryAt = computeNextRetryAt(failureType, currentRetry);\n const isFatal = !nextRetryAt || failureType === FailureType.FATAL;\n\n await prismaAdmin.platformJob.update({\n where: { id: jobId },\n data: {\n status: isFatal ? 'FAILED' : 'RETRY_WAIT',\n error: errorMessage.slice(0, 2000),\n finishedAt: new Date(),\n heartbeatAt: null,\n workerId: null,\n nextRetryAt,\n retryCount: { increment: 1 },\n failureType,\n dlq: isFatal,\n dlqAt: isFatal ? new Date() : null,\n dlqReason: isFatal ? errorMessage.slice(0, 1000) : null,\n ...(checkpointData ? { checkpoint: checkpointData, checkpointAt: new Date() } : {}),\n result: {\n ...(jobRow?.result as any ?? {}),\n failureType,\n ...(checkpointData ? { checkpoint: checkpointData } : {})\n } as any,\n },\n }).catch((e) => logger.error({ err: e, jobId }, '[job-queue] failed to record job failure'));\n}\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// RFC 6238 TOTP \u2014 no external dependency, uses Node built-in crypto.\nexport function generateTOTP(secret: string): string {\n const BASE32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';\n const clean = secret.toUpperCase().replace(/\\s/g, '').replace(/=/g, '');\n let bits = 0, value = 0;\n const bytes: number[] = [];\n for (const ch of clean) {\n const idx = BASE32.indexOf(ch);\n if (idx === -1) continue;\n value = (value << 5) | idx;\n bits += 5;\n if (bits >= 8) { bits -= 8; bytes.push((value >> bits) & 0xff); }\n }\n const counter = Math.floor(Date.now() / 30000);\n const buf = Buffer.alloc(8);\n buf.writeBigInt64BE(BigInt(counter));\n const hmac = createHmac('sha1', Buffer.from(bytes)).update(buf).digest();\n const offset = hmac[hmac.length - 1] & 0xf;\n const code = ((hmac[offset] & 0x7f) << 24) | ((hmac[offset + 1] & 0xff) << 16) |\n ((hmac[offset + 2] & 0xff) << 8) | (hmac[offset + 3] & 0xff);\n return String(code % 1_000_000).padStart(6, '0');\n}\n\nfunction deriveFallbackTargetPath(ref: string, framework: string): string {\n const slug = (ref ?? 'test').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'test';\n const base = framework.startsWith('cypress') ? 'cypress/e2e/'\n : (framework === 'playwright-py' || framework === 'pytest' || framework === 'selenium-py') ? 'tests/'\n : (framework === 'playwright-java' || framework === 'selenium-java' || framework === 'junit') ? 'src/test/java/'\n : 'tests/';\n const ext = (framework === 'playwright-py' || framework === 'pytest' || framework === 'selenium-py') ? '_test.py'\n : (framework === 'playwright-java' || framework === 'selenium-java' || framework === 'junit') ? 'Test.java'\n : framework.includes('ts') ? '.spec.ts'\n : '.spec.js';\n return `${base}${slug}${ext}`;\n}\n\nfunction makeJobName(type: string): string {\n const now = new Date();\n const date = now.toISOString().slice(0, 10); // YYYY-MM-DD\n const ts = now.getTime();\n const label = type === 'CRAWL' ? 'Crawl'\n : type === 'CAPTURE_STEPS' ? 'CaptureSteps'\n : type === 'NATIVE_CRAWL' ? 'NativeCrawl'\n : type === 'GENERATE_TESTS' ? 'GenerateTests'\n : type === 'VISUAL_REGRESSION' ? 'VisualRegression'\n : type === 'AI_EDIT' ? 'AIEdit'\n : type === 'API_TESTS' ? 'ApiTests'\n : type === 'SECURITY_TESTS' ? 'SecurityTests'\n : type === 'INTEGRATION_TESTS' ? 'IntegrationTests'\n : type === 'GENERATE_TESTS_FROM_REQS' ? 'GenerateTestsFromReqs'\n : type === 'GENERATE_DATA' ? 'GenerateData'\n : type === 'GENERATE_SCRIPT' ? 'GenerateScript'\n : type === 'GENERATE_TEST_PLAN' ? 'GenerateTestPlan'\n : type === 'IMPORT_TESTS' ? 'ImportTests'\n : type === 'GENERATE_SCREEN_TESTS' ? 'GenerateScreenTests'\n : type === 'BATCH_GENERATE_ALIGNED' ? 'BatchGenerateAligned'\n : type === 'CREATE_PR' ? 'CreatePR'\n : type === 'NEW_PAGES_PIPELINE' ? 'NewPagesPipeline'\n : type.charAt(0) + type.slice(1).toLowerCase().replace(/_([a-z])/g, (_, c) => c.toUpperCase());\n return `${label}_${date}_${ts}`;\n}\n\nexport interface PlatformJobConfig {\n tenantId: string;\n projectId: string;\n appUrl?: string; // Optional since generate jobs don't strictly need it\n credentials?: { username: string; password: string; oneTimeCode?: string };\n /** Decrypted Playwright storageState JSON \u2014 if present, session is restored without AI login. */\n storageState?: string;\n screenshotDir?: string;\n maxScreens?: number;\n /** Seed URLs added to the initial crawl queue \u2014 link discovery still runs from each page. */\n startUrls?: string[];\n /** When provided, crawl ONLY these exact URLs (no link-following). Used for selected-screen recrawl. */\n selectiveUrls?: string[];\n /** Device profiles for multi-viewport screenshot pass after main DESKTOP crawl. */\n deviceProfiles?: string[];\n /** HTTP Basic Auth credentials \u2014 injected as Authorization header on same-origin requests. */\n httpBasicUsername?: string;\n httpBasicPassword?: string;\n /** Bearer/API token \u2014 injected as a request header and into localStorage. Skips AI login. */\n authToken?: string;\n /** Header name for authToken (default: 'Authorization'). */\n authTokenHeader?: string;\n /** Prefix for authToken value (default: 'Bearer '). */\n authTokenPrefix?: string;\n /** CAPTCHA solving API key (2captcha or CapSolver). */\n captchaSolverApiKey?: string;\n /** CAPTCHA solving service provider (default: '2captcha'). */\n captchaSolverProvider?: '2captcha' | 'capsolver';\n /**\n * Path to a Chrome user data directory for profile inheritance (local deployments only).\n * When set, the crawler exports the existing session from this profile and injects it.\n */\n chromeProfilePath?: string;\n /** MailSlurp API key for email OTP auto-retrieval. */\n mailslurpApiKey?: string;\n /** MailSlurp inbox ID to poll for OTP emails. */\n mailslurpInboxId?: string;\n /** SSO provider ('okta' | 'azure_ad' | 'generic_oidc'). */\n ssoProvider?: string;\n /** SSO domain or tenant ID. */\n ssoDomain?: string;\n /** SSO OAuth2 client ID. */\n ssoClientId?: string;\n /** SSO OAuth2 client secret (decrypted). */\n ssoClientSecret?: string;\n /** SSO scopes (space-separated). Default: 'openid profile'. */\n ssoScope?: string;\n /** HTTP/HTTPS/SOCKS5 proxy URL \u2014 routes browser through this proxy for bot-protected sites. */\n proxyUrl?: string;\n // \u2500\u2500\u2500 scrapegraph gap fields \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** HTML extraction mode. 'normal'=raw, 'reader'=Readability article, 'prune'=strip nav/ads. Default: 'normal'. */\n htmlMode?: 'normal' | 'reader' | 'prune';\n /** Per-request screenshot dimensions and quality. Defaults: 1440\u00D7900, quality 80. */\n screenshotConfig?: {\n fullPage?: boolean;\n width?: number;\n height?: number;\n quality?: number;\n };\n /** Enable/disable Playwright stealth plugin. Default: true (existing behaviour). */\n stealth?: boolean;\n /** Mock mode \u2014 intercept all navigation and return synthetic content. Good for CI. */\n mock?: boolean | { minKb?: number; maxKb?: number; minSleep?: number; maxSleep?: number };\n /** MIME type allowlist \u2014 crawler skips URLs returning disallowed content types. */\n allowedTypes?: string[];\n /** Glob-style include patterns for URL paths (e.g. '/blog/*'). Only matching paths crawled. */\n includePatterns?: string[];\n /** Glob-style exclude patterns for URL paths (e.g. '/admin/*'). Matching paths skipped. */\n excludePatterns?: string[];\n /** Allow crawler to follow links to external domains. Default: false. */\n allowExternal?: boolean;\n /** Requested output formats for a single-pass multi-format extraction. */\n requestedFormats?: Array<'markdown' | 'html' | 'links' | 'images' | 'summary' | 'json' | 'branding' | 'screenshot'>;\n /** LLM chunker configuration for AI extraction passes. */\n chunkerConfig?: { size?: number | 'dynamic'; overlap?: number };\n /** ISO-3166-1 alpha-2 country code for geo-targeted proxy routing (e.g. 'us', 'gb'). */\n proxyCountry?: string;\n /** When true, after the primary crawl auto-enqueue one CRAWL job per credentialProfile. */\n crawlAllRoles?: boolean;\n /** Decrypted credential profiles used when crawlAllRoles is true. */\n credentialProfiles?: Array<{ id: string; name: string; username: string; password: string }>;\n /** Set on role-crawl sub-jobs \u2014 profile name tagged on discovered screens via roleAccess column. */\n roleProfileName?: string;\n}\n\n/** In-memory map of jobId \u2192 AbortController for RUNNING jobs. */\nconst runningControllers = new Map<string, AbortController>();\n\n// \u2500\u2500\u2500 Durable job worker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// A minimal Postgres-backed work queue: atomic claim via `FOR UPDATE SKIP\n// LOCKED` (safe for any number of concurrent pollers, in-process or across\n// containers), plus a stale-job sweep keyed off a liveness heartbeat rather\n// than wall-clock job age, so a slow-but-alive job is never requeued and\n// double-processed. See startJobWorkerLoop() below.\n\nconst WORKER_ID = `${os.hostname()}:${process.pid}`;\nconst STALE_JOB_TIMEOUT_MINUTES = Number(process.env.JOB_STALE_TIMEOUT_MINUTES) || 30;\nconst MAX_CONCURRENT_PER_TENANT = Number(process.env.MAX_CONCURRENT_PER_TENANT) || 4;\nconst WORKER_POLL_INTERVAL_MS = Number(process.env.JOB_WORKER_POLL_INTERVAL_MS) || 15_000;\nconst WORKER_CONCURRENCY = Number(process.env.JOB_WORKER_CONCURRENCY) || 8;\n\n// GAP-12: true active-execution semaphore \u2014 counts jobs currently executing\n// (claim\u2192finish), not poll slots. A single poll loop checks this before every\n// claim so the cap is never exceeded even across overlapping ticks.\nlet activeExecutions = 0;\n\nfunction withSemaphore<T>(fn: () => Promise<T>): Promise<T> {\n activeExecutions++;\n return fn().finally(() => { activeExecutions--; });\n}\n\nexport function getActiveExecutionCount(): number {\n return activeExecutions;\n}\n\n// Max simultaneous DISCOVER jobs \u2014 each launches a Playwright browser and holds\n// DB connections, so running many concurrently exhausts resources. Default 2.\nconst MAX_CONCURRENT_DISCOVER = Number(process.env.MAX_CONCURRENT_DISCOVER) || 2;\nconst JOB_HEARTBEAT_INTERVAL_MS = Number(process.env.JOB_HEARTBEAT_INTERVAL_MS) || 15_000;\n// How many headless browsers a single RUN_TEST_SUITE job launches at once \u2014\n// distinct from WORKER_CONCURRENCY (how many *jobs* the poll loop claims at\n// once). Bounded by default to avoid exhausting a container's memory; the\n// primary scale-out path for running more tests at once is still horizontal\n// (more worker containers / SQS), not raising this past what one container\n// can safely hold \u2014 see startJobWorkerLoop's doc comment.\nconst TEST_EXEC_CONCURRENCY = Number(process.env.TEST_EXEC_CONCURRENCY) || 5;\n\ntype ClaimedJob = { id: string; tenantId: string; projectId: string; type: string; config: any };\n\n/** Dispatch table populated at module bottom, once every process*Job function is declared. */\nlet jobDispatchTable: Record<string, (jobId: string, config: any) => Promise<void>> | null = null;\n\n/** Atomically claims one specific QUEUED job for this worker. Returns null if it was already claimed. */\nexport async function claimSpecificJob(jobId: string): Promise<ClaimedJob | null> {\n const rows = await prismaAdmin.$queryRaw<ClaimedJob[]>`\n UPDATE \"PlatformJob\"\n SET status = 'RUNNING', \"startedAt\" = NOW(), \"heartbeatAt\" = NOW(), \"workerId\" = ${WORKER_ID}\n WHERE id = ${jobId} AND status = 'QUEUED'\n AND (\"nextRetryAt\" IS NULL OR \"nextRetryAt\" <= NOW())\n RETURNING id, \"tenantId\" AS \"tenantId\", \"projectId\" AS \"projectId\", type, config\n `;\n return rows[0] ?? null;\n}\n\n/** Atomically claims the oldest QUEUED job across all tenants. SKIP LOCKED makes this\n * race-safe against any number of other pollers doing the same thing concurrently.\n * DISCOVER jobs are subject to MAX_CONCURRENT_DISCOVER \u2014 skipped when that many are\n * already RUNNING to prevent Playwright/DB pool exhaustion from simultaneous crawls. */\nexport async function claimNextQueuedJob(): Promise<ClaimedJob | null> {\n const maxDiscover = MAX_CONCURRENT_DISCOVER;\n const rows = await prismaAdmin.$queryRaw<ClaimedJob[]>`\n UPDATE \"PlatformJob\"\n SET status = 'RUNNING', \"startedAt\" = NOW(), \"heartbeatAt\" = NOW(), \"workerId\" = ${WORKER_ID}\n WHERE id = (\n SELECT id FROM \"PlatformJob\" p\n WHERE status = 'QUEUED'\n AND (\"nextRetryAt\" IS NULL OR \"nextRetryAt\" <= NOW())\n AND (\n type != 'DISCOVER'\n OR (SELECT COUNT(*) FROM \"PlatformJob\" WHERE status = 'RUNNING' AND type = 'DISCOVER') < ${maxDiscover}\n )\n AND (\n SELECT COUNT(*) FROM \"PlatformJob\" p2\n WHERE p2.status = 'RUNNING'\n AND p2.\"tenantId\" = p.\"tenantId\"\n AND p2.inline = false\n ) < ${MAX_CONCURRENT_PER_TENANT}\n ORDER BY p.\"priority\" DESC, p.\"queuedAt\" ASC\n FOR UPDATE SKIP LOCKED\n LIMIT 1\n )\n RETURNING id, \"tenantId\" AS \"tenantId\", \"projectId\" AS \"projectId\", type, config\n `;\n return rows[0] ?? null;\n}\n\n/** Requeues RUNNING jobs whose heartbeat has gone stale \u2014 the worker that claimed them\n * died (crash, restart, OOM) without ever marking them DONE/FAILED. Idempotent and\n * safe to run from every worker process; only affects rows past the timeout.\n * API-in-process job types (GENERATE_SCRIPT etc.) are failed directly \u2014 requeueing them\n * would cause the worker to claim and fail them with a confusing \"claimed by worker\" error. */\nexport async function requeueStaleRunningJobs(): Promise<void> {\n // Directly fail stale API-in-process jobs \u2014 worker cannot process these types.\n const failedRows = await prismaAdmin.$queryRaw<Array<{ id: string }>>`\n UPDATE \"PlatformJob\"\n SET status = 'FAILED',\n \"finishedAt\" = NOW(),\n error = 'API process was restarted while this job was running \u2014 please retry from the UI.'\n WHERE status = 'RUNNING'\n AND type IN ('GENERATE_SCRIPT', 'GENERATE_DATA', 'GENERATE_TESTS', 'GENERATE_TEST_PLAN')\n AND COALESCE(\"heartbeatAt\", \"startedAt\") < (NOW() AT TIME ZONE 'UTC') - (${STALE_JOB_TIMEOUT_MINUTES} * interval '1 minute')\n RETURNING id\n `;\n if (failedRows.length > 0) {\n logger.warn({ jobIds: failedRows.map((r: any) => r.id), staleTimeoutMinutes: STALE_JOB_TIMEOUT_MINUTES }, '[job-worker] failed stale API-in-process job(s) \u2014 API restarted while running');\n }\n\n const rows = await prismaAdmin.$queryRaw<Array<{ id: string }>>`\n UPDATE \"PlatformJob\"\n SET status = 'QUEUED',\n \"workerId\" = NULL,\n result = jsonb_set(\n COALESCE(result::jsonb, '{}'::jsonb),\n '{step}',\n to_jsonb('Worker heartbeat lost \u2014 queued for recovery'::text),\n true\n )\n WHERE status = 'RUNNING'\n AND type NOT IN ('GENERATE_SCRIPT', 'GENERATE_DATA', 'GENERATE_TESTS', 'GENERATE_TEST_PLAN')\n AND COALESCE(\"heartbeatAt\", \"startedAt\") < (NOW() AT TIME ZONE 'UTC') - (${STALE_JOB_TIMEOUT_MINUTES} * interval '1 minute')\n RETURNING id\n `;\n if (rows.length > 0) {\n logger.warn({ jobIds: rows.map((r: any) => r.id), staleTimeoutMinutes: STALE_JOB_TIMEOUT_MINUTES }, '[job-worker] requeued stale RUNNING job(s)');\n }\n}\n\n/**\n * Requeues any RUNNING jobs claimed by a different worker (i.e., a previous\n * process instance). Call once at startup before the poll loop begins \u2014 this\n * provides immediate recovery instead of waiting the full STALE_JOB_TIMEOUT_MINUTES.\n * Safe to call from multiple replicas simultaneously (each uses its own WORKER_ID).\n */\nexport async function requeueOrphanedJobs(): Promise<void> {\n const rows = await prismaAdmin.$queryRaw<Array<{ id: string }>>`\n UPDATE \"PlatformJob\"\n SET status = 'QUEUED',\n \"workerId\" = NULL,\n result = jsonb_set(\n COALESCE(result::jsonb, '{}'::jsonb),\n '{step}',\n to_jsonb('Worker restarted \u2014 queued for recovery'::text),\n true\n )\n WHERE status = 'RUNNING'\n AND \"workerId\" IS NOT NULL\n AND \"workerId\" != ${WORKER_ID}\n RETURNING id\n `;\n if (rows.length > 0) {\n logger.warn({ jobIds: rows.map((r: any) => r.id), workerId: WORKER_ID }, '[job-worker] requeued orphaned RUNNING job(s) from dead worker');\n }\n}\n\n/** Dispatches a claimed job to its type's processor. Last-resort safety net \u2014 every\n * process*Job function already catches and records its own failures. */\nexport async function dispatchClaimedJob(job: ClaimedJob): Promise<void> {\n const dispatcher = jobDispatchTable?.[job.type];\n if (!dispatcher) {\n logger.error({ jobId: job.id, type: job.type }, '[job-worker] no dispatcher registered for job type');\n await prismaAdmin.platformJob.update({\n where: { id: job.id },\n data: { status: 'FAILED', error: `Unknown job type: ${job.type}`, finishedAt: new Date() },\n }).catch(() => { });\n return;\n }\n const stopTimer = crawlJobDurationSeconds.startTimer({ type: job.type });\n try {\n await dispatcher(job.id, job.config);\n stopTimer();\n // dispatcher() resolving doesn't mean the job succeeded \u2014 every process*Job\n // function catches and records its own failures internally without\n // rethrowing (see this function's doc comment). Read back the actual\n // final status so the metric reflects reality, not just \"didn't throw\".\n const finished = await prismaAdmin.platformJob.findUnique({ where: { id: job.id }, select: { status: true } }).catch(() => null);\n crawlJobsTotal.inc({ type: job.type, status: (finished?.status === 'FAILED' || finished?.status === 'RETRY_WAIT') ? 'failed' : 'success' });\n } catch (err: any) {\n stopTimer();\n crawlJobsTotal.inc({ type: job.type, status: 'failed' });\n logger.error({ jobId: job.id, type: job.type, err: String(err?.message ?? err) }, '[job-worker] job threw uncaught error');\n await recordJobFailure(job.tenantId, job.id, err);\n }\n}\n\nlet workerLoopTimer: ReturnType<typeof setInterval> | null = null;\n// GAP-12: signals the semaphore-based poll loop to exit gracefully.\nlet workerShuttingDown = false;\n\n/** Re-promotes FAILED jobs to RETRY_WAIT if they are eligible for retry based on per-type limits.\n * RETRY_WAIT makes the waiting period visible in the UI. A separate sweep then promotes\n * RETRY_WAIT \u2192 QUEUED once nextRetryAt <= NOW(). */\nexport async function retryEligibleFailedJobs(): Promise<void> {\n // Legacy path: jobs that failed before failure-type classification was added.\n // New failures go FAILED\u2192RETRY_WAIT directly in the job error handler (dispatchClaimedJob).\n // This only picks up old FAILED rows that have no nextRetryAt set yet.\n // Phase 1: FAILED \u2192 RETRY_WAIT (GAP-07: visible waiting state)\n const rows = await prismaAdmin.$queryRaw<Array<{ id: string }>>`\n UPDATE \"PlatformJob\"\n SET status = 'RETRY_WAIT',\n \"retryCount\" = \"retryCount\" + 1,\n \"nextRetryAt\" = NOW() + (INTERVAL '1 minute' * POWER(2, \"retryCount\")),\n \"workerId\" = NULL\n WHERE status = 'FAILED'\n AND dlq = false\n AND \"nextRetryAt\" IS NULL\n AND \"retryCount\" < 5\n RETURNING id\n `;\n if (rows.length > 0) {\n logger.info({ jobIds: rows.map((r: any) => r.id) }, '[job-worker] moved failed job(s) to RETRY_WAIT');\n }\n\n // Phase 2: RETRY_WAIT \u2192 QUEUED when the wait period has elapsed (GAP-07)\n const readyRows = await prismaAdmin.$queryRaw<Array<{ id: string }>>`\n UPDATE \"PlatformJob\"\n SET status = 'QUEUED',\n \"requeuedAt\" = NOW()\n WHERE status = 'RETRY_WAIT'\n AND \"nextRetryAt\" <= (NOW() AT TIME ZONE 'UTC')\n AND dlq = false\n RETURNING id\n `;\n if (readyRows.length > 0) {\n logger.info({ jobIds: readyRows.map((r: any) => r.id) }, '[job-worker] promoted RETRY_WAIT job(s) to QUEUED');\n }\n}\n\n/** Promotes FAILED jobs to DLQ if they exceed their per-type retry limits. */\nexport async function promoteToDeadLetterQueue(): Promise<void> {\n const rows = await prismaAdmin.$queryRaw<Array<{ id: string }>>`\n UPDATE \"PlatformJob\"\n SET dlq = true,\n \"dlqAt\" = NOW(),\n \"dlqReason\" = error\n WHERE status = 'FAILED'\n AND dlq = false\n AND \"retryCount\" >= CASE\n WHEN type IN ('CRAWL', 'NATIVE_CRAWL') THEN 3\n WHEN type IN ('GENERATE_TESTS', 'IMPORT_TESTS', 'RUN_TEST_SUITE', 'VISUAL_REGRESSION') THEN 2\n WHEN type IN ('GENERATE_SCRIPT', 'GENERATE_DATA', 'GENERATE_TEST_PLAN') THEN 0\n ELSE 1\n END\n RETURNING id\n `;\n if (rows.length > 0) {\n logger.error({ jobIds: rows.map((r: any) => r.id) }, '[job-worker] promoted failed job(s) to DLQ');\n }\n}\n\n/**\n * Starts the durable job worker poll loop. This is what makes job processing\n * survive a restart \u2014 enqueueJob()'s immediate same-process claim attempt is\n * only a latency optimization; if the process dies before that fires (or\n * mid-job), this loop's next tick claims/requeues it. Call once at process\n * startup (API server and/or a dedicated crawler worker process \u2014 safe to\n * run in both, and safe to run in many container replicas at once).\n */\nexport function startJobWorkerLoop(opts?: { pollIntervalMs?: number; concurrency?: number }): void {\n if (workerLoopTimer) return; // already running in this process\n const pollIntervalMs = opts?.pollIntervalMs ?? WORKER_POLL_INTERVAL_MS;\n const concurrency = opts?.concurrency ?? WORKER_CONCURRENCY;\n logger.info({ workerId: WORKER_ID, pollIntervalMs, concurrency }, '[job-worker] starting');\n\n // GAP-12: maintenance sweep stays on a fixed interval (unchanged).\n const sweepTick = async () => {\n try {\n await retryEligibleFailedJobs();\n await promoteToDeadLetterQueue();\n await requeueStaleRunningJobs();\n } catch (err) {\n logger.error({ err: String(err) }, '[job-worker] stale/retry job sweep failed');\n }\n };\n workerLoopTimer = setInterval(sweepTick, pollIntervalMs);\n\n // GAP-12: single continuous poll loop \u2014 checks the semaphore before every\n // claim so activeExecutions never exceeds WORKER_CONCURRENCY regardless of\n // how long individual jobs take. Jobs are non-blocking: withSemaphore(...)\n // is NOT awaited here, so the loop immediately loops back and can claim the\n // next job while previous ones are still running.\n //\n // NOTE (GAP-20): requeueOrphanedJobs() is intentionally NOT called at startup here.\n // Calling it at startup is unsafe in multi-replica deployments \u2014 replica A would steal\n // RUNNING jobs from replica B (which is still alive and processing them), causing\n // double-execution. The stale-job sweep (requeueStaleRunningJobs) handles recovery\n // safely via heartbeat timeout. requeueOrphanedJobs() remains available for admin use.\n workerShuttingDown = false;\n\n const pollLoop = async () => {\n void sweepTick(); // run one sweep immediately on startup\n while (!workerShuttingDown) {\n if (activeExecutions >= concurrency) {\n // All slots busy \u2014 wait before checking again without hammering the DB.\n await new Promise(resolve => setTimeout(resolve, 500));\n continue;\n }\n\n let paused = false;\n try {\n const pauseRow = await prismaAdmin.systemConfig.findUnique({ where: { key: 'queue.paused' } });\n paused = pauseRow?.value === 'true';\n } catch {\n // ignore \u2014 treat as unpaused\n }\n\n if (paused) {\n logger.info('[job-worker] queue is paused \u2014 skipping claim');\n await new Promise(resolve => setTimeout(resolve, pollIntervalMs));\n continue;\n }\n\n let job: ClaimedJob | null = null;\n try {\n job = await claimNextQueuedJob();\n } catch (err) {\n logger.error({ err: String(err) }, '[job-worker] claim failed');\n }\n\n if (!job) {\n // Nothing queued \u2014 back off before polling again.\n await new Promise(resolve => setTimeout(resolve, pollIntervalMs));\n continue;\n }\n\n // Non-blocking: fire and forget into semaphore \u2014 poll loop continues immediately.\n withSemaphore(() => dispatchClaimedJob(job!)).catch(err => {\n logger.error({ err, jobId: job!.id }, '[worker] unhandled job error');\n });\n }\n };\n\n void pollLoop();\n}\n\n/** Stops the poll loop. Mainly for tests. */\nexport function stopJobWorkerLoop(): void {\n workerShuttingDown = true; // GAP-12: signal continuous poll loop to exit\n if (workerLoopTimer) clearInterval(workerLoopTimer);\n workerLoopTimer = null;\n}\n\n/** Create a job record that tracks a synchronous operation (telemetry only, no background processor). */\nexport async function createTrackedJob(\n type: string,\n config: { tenantId: string; projectId: string;[k: string]: any }\n): Promise<string> {\n const name = makeJobName(type);\n const job = await withTenant(config.tenantId, (tx) =>\n tx.platformJob.create({\n data: { tenantId: config.tenantId, projectId: config.projectId, type, name, status: 'RUNNING', startedAt: new Date(), inline: true, config: config as any },\n })\n ) as { id: string };\n return job.id;\n}\n\n/** Mark a tracked job DONE or FAILED. Safe to call in finally \u2014 swallows errors.\n * GAP-14/15: Uses failure-type classification for per-type backoff on FAILED. */\nexport async function finishTrackedJob(tenantId: string, jobId: string, error?: string): Promise<void> {\n if (!error) {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', finishedAt: new Date(), progress: 100 } })\n ).catch(() => { });\n return;\n }\n const failureType = classifyFailure(error);\n const jobRow = await prismaAdmin.platformJob.findUnique({ where: { id: jobId }, select: { retryCount: true, result: true } }).catch(() => null);\n const currentRetry = jobRow?.retryCount ?? 0;\n const nextRetryAt = computeNextRetryAt(failureType, currentRetry);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: nextRetryAt ? 'RETRY_WAIT' : 'FAILED',\n finishedAt: new Date(),\n error,\n nextRetryAt,\n retryCount: { increment: 1 },\n failureType,\n result: { ...(jobRow?.result as any ?? {}), failureType } as any,\n },\n })\n ).catch(() => { });\n if (!nextRetryAt || failureType === FailureType.FATAL) {\n await prismaAdmin.platformJob.update({\n where: { id: jobId },\n data: { dlq: true, dlqAt: new Date(), dlqReason: error },\n }).catch(() => { });\n }\n}\n\n/** Enqueue a generic platform job \u2014 returns the job ID immediately. */\nexport async function enqueueJob(type: string, config: any): Promise<string> {\n const { tenantId, projectId, submittedBy } = config;\n\n const name = makeJobName(type);\n const job = await withTenant(tenantId, (tx) =>\n tx.platformJob.create({\n data: {\n tenantId,\n projectId,\n type,\n name,\n status: 'QUEUED',\n config: config as any,\n ...(submittedBy ? { submittedBy } : {}),\n },\n })\n ) as { id: string };\n\n // Durability comes from startJobWorkerLoop()'s poll \u2014 this is only a same-process\n // latency optimization so a job doesn't sit idle until the next poll tick. The\n // claim is atomic (WHERE status='QUEUED'), so it's safe even if the poll loop's\n // own tick fires concurrently and claims this same job first.\n if (process.env.JOB_QUEUE_BACKEND === 'sqs') {\n const { sendJobToSqs } = await import('./sqs-queue.js');\n await sendJobToSqs(job.id);\n } else if (process.env.DISABLE_WORKER !== 'true') {\n // Skip inline dispatch when DISABLE_WORKER=true \u2014 the dedicated crawler process\n // will pick this up via its poll loop. Inline dispatch in the API container\n // would launch Playwright browsers and crash the API.\n setImmediate(() => claimSpecificJob(job.id).then((claimed) => claimed && dispatchClaimedJob(claimed)));\n }\n\n return job.id;\n}\n\n/** Get the current state of a platform job. */\nexport async function getJob(tenantId: string, jobId: string) {\n return withTenant(tenantId, (tx) =>\n tx.platformJob.findFirst({ where: { id: jobId, tenantId } })\n );\n}\n\n/** List recent jobs for a project. */\nexport async function listJobs(tenantId: string, projectId: string) {\n return withTenant(tenantId, async (tx) => {\n const jobs = await tx.platformJob.findMany({\n where: { tenantId, projectId },\n orderBy: { queuedAt: 'desc' },\n take: 50,\n });\n // Attach the most-recently-used model for each job from LLMCallLog\n const jobIds = jobs.map((j) => j.id);\n if (jobIds.length === 0) return jobs;\n const modelRows = await (tx as any).lLMCallLog.groupBy({\n by: ['jobId'],\n where: { tenantId, jobId: { in: jobIds } },\n _max: { model: true, calledAt: true },\n });\n const modelByJobId = new Map<string, string>(\n modelRows\n .filter((r: any) => r.jobId && r._max?.model)\n .map((r: any) => [r.jobId as string, r._max.model as string])\n );\n return jobs.map((j) => ({ ...j, llmModel: modelByJobId.get(j.id) ?? null }));\n });\n}\n\n/** Cancel a RUNNING or QUEUED job.\n * Sets status to CANCEL_REQUESTED (GAP-08) so the worker's abort handler can\n * detect the request and set CANCELLED + finishedAt after the underlying work stops.\n * The in-process AbortController is also triggered for same-process jobs. */\nexport async function cancelJob(tenantId: string, jobId: string): Promise<void> {\n // Abort in-memory controller if job is running in the same process\n const controller = runningControllers.get(jobId);\n if (controller) {\n controller.abort();\n runningControllers.delete(jobId);\n }\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'CANCEL_REQUESTED' },\n })\n );\n}\n\n/** Re-enqueue a job using the same config as a previous job. */\nexport async function rerunJob(tenantId: string, jobId: string): Promise<string> {\n const job = await withTenant(tenantId, (tx) =>\n tx.platformJob.findUnique({ where: { id: jobId } })\n ) as { type: string; config: any } | null;\n if (!job) throw new Error('Job not found');\n return enqueueJob(job.type, { ...(job.config as any), tenantId });\n}\n\n// In-memory checkpoint store: updated every 10 screens, saved to DB result on failure.\nconst jobCheckpoints = new Map<string, { visitedUrls: string[]; pendingQueue: string[] }>();\n\nexport async function resumeJob(tenantId: string, jobId: string): Promise<string> {\n const job = await withTenant(tenantId, (tx) =>\n tx.platformJob.findUnique({ where: { id: jobId } })\n ) as { type: string; config: any; result: any; checkpoint?: any } | null;\n if (!job) throw new Error('Job not found');\n\n const checkpoint = (job as any).checkpoint ?? (job.result as any)?.checkpoint;\n const visitedUrls: string[] = checkpoint?.visitedUrls ?? [];\n const pendingQueue: string[] = checkpoint?.pendingQueue ?? [];\n\n if (pendingQueue.length === 0 && visitedUrls.length === 0) {\n // No checkpoint saved \u2014 full rerun\n return enqueueJob(job.type, { ...(job.config as any), tenantId });\n }\n\n return enqueueJob(job.type, {\n ...(job.config as any),\n tenantId,\n skipUrls: [...((job.config as any)?.skipUrls ?? []), ...visitedUrls],\n startUrls: pendingQueue.length > 0 ? pendingQueue : (job.config as any)?.startUrls,\n });\n}\n\n/** Update progress percentage and current step label on a running job (non-fatal). */\nexport async function updateJobProgress(tenantId: string, jobId: string, progress: number, step: string) {\n try {\n await withTenant(tenantId, async (tx) => {\n // DB-flag cancellation: works for both inline and SQS backends.\n // When a user cancels, status is set to CANCEL_REQUESTED (GAP-08).\n // This check fires at each progress update (every few seconds during crawl).\n const current = await tx.platformJob.findUnique({\n where: { id: jobId },\n select: { status: true },\n });\n if (current?.status === 'CANCELLED' || current?.status === 'CANCEL_REQUESTED') {\n runningControllers.get(jobId)?.abort();\n runningControllers.delete(jobId);\n return; // abort signal propagates; crawlProject exits on next signal check\n }\n await tx.platformJob.update({\n where: { id: jobId },\n // GAP-09: write currentStep as a proper column in addition to result.step (backward compat)\n data: { progress, currentStep: step, result: { step } as any, heartbeatAt: new Date() },\n });\n });\n } catch { /* non-fatal \u2014 progress updates best-effort */ }\n}\n\n/** Bump only worker liveness. This intentionally does not touch progress/result. */\nasync function heartbeatJob(tenantId: string, jobId: string) {\n try {\n await withTenant(tenantId, async (tx) => {\n const current = await tx.platformJob.findUnique({\n where: { id: jobId },\n select: { status: true },\n });\n if (current?.status === 'CANCELLED' || current?.status === 'CANCEL_REQUESTED') {\n runningControllers.get(jobId)?.abort();\n runningControllers.delete(jobId);\n return;\n }\n if (current?.status !== 'RUNNING') return;\n await tx.platformJob.update({\n where: { id: jobId },\n data: { heartbeatAt: new Date(), workerId: WORKER_ID },\n });\n });\n } catch { /* non-fatal \u2014 heartbeat updates best-effort */ }\n}\n\n/** Background: for each changed screen, search Jira + Confluence for related tickets/pages. */\nasync function investigateScreenChanges(\n tenantId: string,\n projectId: string,\n changedScreens: Array<{ id: string; name: string; url?: string; addedElements: string[]; removedElements: string[] }>\n) {\n const [jiraCfg, confluenceCfg] = await Promise.all([\n AppBrain.getConnectorConfig(tenantId, projectId, 'JIRA'),\n AppBrain.getConnectorConfig(tenantId, projectId, 'CONFLUENCE'),\n ]);\n\n for (const screen of changedScreens) {\n const searchTerm = screen.url ?? screen.name;\n const changeSummary = `Screen \"${screen.name}\" changed: +${screen.addedElements.length} elements, -${screen.removedElements.length} elements`;\n\n const detail: Record<string, any> = {\n screenId: screen.id,\n screenName: screen.name,\n url: screen.url,\n addedElements: screen.addedElements,\n removedElements: screen.removedElements,\n jiraTickets: [],\n confluencePages: [],\n };\n\n if (jiraCfg?.baseUrl && jiraCfg?.email && jiraCfg?.apiToken) {\n try {\n detail.jiraTickets = await jira.searchIssues(\n { baseUrl: jiraCfg.baseUrl, email: jiraCfg.email, apiToken: jiraCfg.apiToken, projectKey: jiraCfg.projectKey },\n searchTerm\n );\n } catch (e) {\n logger.warn({ searchTerm, err: e }, 'Jira search failed');\n }\n }\n\n if (confluenceCfg?.baseUrl && confluenceCfg?.email && confluenceCfg?.apiToken) {\n try {\n detail.confluencePages = await confluence.searchPages(\n { baseUrl: confluenceCfg.baseUrl, email: confluenceCfg.email, apiToken: confluenceCfg.apiToken, spaceKey: confluenceCfg.spaceKey },\n searchTerm\n );\n } catch (e) {\n logger.warn({ searchTerm, err: e }, 'Confluence search failed');\n }\n }\n\n await AppBrain.logChange(tenantId, projectId, 'MODIFIED', changeSummary, detail);\n logger.info({ screen: screen.name, jiraTickets: detail.jiraTickets.length, confluencePages: detail.confluencePages.length }, 'Logged screen change investigation');\n }\n}\n\nexport interface ProcessJobOpts {\n onProgress?: (pct: number) => Promise<void>;\n}\n\nexport async function processJob(jobId: string, config: any, opts?: ProcessJobOpts) {\n const { tenantId, projectId } = config;\n\n // Mark as RUNNING \u2014 skip if already CANCELLED (race: user cancelled before worker picked it up).\n const jobStartedAt = new Date();\n const claimed = await withTenant(tenantId, (tx) =>\n tx.platformJob.updateMany({\n where: { id: jobId, status: { not: 'CANCELLED' } },\n data: { status: 'RUNNING', startedAt: jobStartedAt, heartbeatAt: jobStartedAt, workerId: WORKER_ID },\n })\n );\n if (claimed.count === 0) return;\n\n const controller = new AbortController();\n runningControllers.set(jobId, controller);\n const heartbeatTimer = setInterval(() => {\n void heartbeatJob(tenantId, jobId);\n }, JOB_HEARTBEAT_INTERVAL_MS);\n\n const onProgress = async (pct: number, step: string) => {\n await updateJobProgress(tenantId, jobId, pct, step);\n await opts?.onProgress?.(pct);\n };\n\n const DEFAULT_MAX_SCREENS = parseInt(process.env.CRAWL_DEFAULT_MAX_SCREENS ?? '') || 150;\n\n try {\n // Per-project maxScreens + deviceProfiles + seedUrls + interactive probing + sitemap seeding + traversal strategy from crawlConfig override job config defaults.\n if (!config.maxScreens || !config.deviceProfiles || config.interactive === undefined || config.useSitemap === undefined || !config.traversalStrategy) {\n const proj: any = await withTenant(tenantId, (tx) =>\n (tx as any).project.findUnique({ where: { id: projectId }, select: { crawlConfig: true } })\n );\n const perProject = proj?.crawlConfig?.maxScreens;\n if (!config.maxScreens) config.maxScreens = perProject || DEFAULT_MAX_SCREENS;\n if (!config.deviceProfiles) config.deviceProfiles = proj?.crawlConfig?.deviceProfiles ?? ['DESKTOP'];\n if (config.interactive === undefined) config.interactive = proj?.crawlConfig?.interactive ?? false;\n if (config.useSitemap === undefined) config.useSitemap = proj?.crawlConfig?.useSitemap ?? false;\n if (!config.sitemapUrl) config.sitemapUrl = proj?.crawlConfig?.sitemapUrl ?? undefined;\n if (!config.traversalStrategy) config.traversalStrategy = proj?.crawlConfig?.traversalStrategy ?? 'BFS';\n if (!config.bestFirstKeywords) config.bestFirstKeywords = proj?.crawlConfig?.bestFirstKeywords ?? undefined;\n // segmentSize from crawl config \u2014 default unlimited (9_999_999) so normal sites run in one job;\n // only segment if the project explicitly sets a segmentSize in its crawl config.\n if (!config.segmentSize) config.segmentSize = proj?.crawlConfig?.segmentSize ?? 9_999_999;\n // Load saved seedUrls as non-selective seeds (link discovery still runs from them).\n // Skip when a selective recrawl is requested (selectiveUrls set) or seeds already provided.\n if (!config.selectiveUrls?.length && (!config.startUrls || config.startUrls.length === 0)) {\n const saved: string[] = proj?.crawlConfig?.seedUrls ?? [];\n if (saved.length > 0) config.startUrls = saved;\n }\n if (config.interactive === undefined) config.interactive = proj?.crawlConfig?.interactive ?? false;\n }\n // Build runtime credentials: convert stored totpSecret \u2192 getOneTimeCode function\n const runtimeCreds = config.credentials\n ? {\n ...config.credentials,\n ...(config.credentials.totpSecret\n ? { getOneTimeCode: async () => generateTOTP(config.credentials.totpSecret) }\n : {}),\n }\n : undefined;\n\n const storageConfigForCrawl = await getTenantStorageConfig(tenantId).catch(() => null);\n const result = await crawlProject({\n ...config, credentials: runtimeCreds, abortSignal: controller.signal, jobId,\n storageConfig: storageConfigForCrawl,\n onCheckpoint: async (visitedUrls, pendingQueue) => {\n jobCheckpoints.set(jobId, { visitedUrls, pendingQueue });\n // GAP-19: configurable checkpoint interval; GAP-04: persist to DB for crash recovery\n const CHECKPOINT_INTERVAL = parseInt(process.env.CRAWL_CHECKPOINT_INTERVAL ?? '10');\n if (visitedUrls.length % CHECKPOINT_INTERVAL === 0) {\n try {\n await prismaAdmin.platformJob.update({\n where: { id: jobId },\n data: {\n checkpoint: { visitedUrls, pendingQueue } as any,\n checkpointAt: new Date(),\n },\n });\n } catch { /* non-fatal */ }\n }\n },\n }, onProgress);\n jobCheckpoints.delete(jobId);\n\n const pendingUrls: string[] = (result as any)?.pendingUrls ?? [];\n let nextJobId: string | undefined;\n if (pendingUrls.length > 0) {\n // visitedUrls includes all attempted URLs (screens, soft-404s, API paths) \u2014 more complete than\n // discovered-with-screenId only, preventing the continuation job from re-attempting dead ends.\n const visitedForSkip: string[] = (result as any).visitedUrls ?? ((result as any).discovered ?? [])\n .filter((d: any) => !!d.screenId && !!d.url)\n .map((d: any) => { try { const _u = new URL(d.url as string); _u.hash = ''; return _u.href; } catch { return d.url as string; } });\n const reason = (result as any).wasTimeout ? 'timeout' : 'segment-limit';\n logger.info({ pendingUrls: pendingUrls.length, visited: visitedForSkip.length, reason }, '[crawl] auto-enqueuing continuation job');\n nextJobId = await enqueueJob('CRAWL', {\n ...config,\n startUrls: pendingUrls,\n skipUrls: [...(config.skipUrls ?? []), ...visitedForSkip],\n }).catch((autoErr: any) => { logger.warn({ err: autoErr?.message }, '[crawl] auto-segment enqueue failed (non-fatal)'); return undefined; });\n }\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE',\n result: { ...(result as any), ...(nextJobId ? { nextJobId } : {}) } as any,\n progress: 100,\n finishedAt: new Date(),\n },\n })\n );\n clearInterval(heartbeatTimer);\n runningControllers.delete(jobId);\n\n {\n // Auto-seed: merge all successfully crawled URLs into crawlConfig.seedUrls so future\n // crawls and discovery agents start from a complete picture of the app.\n const crawledUrls: string[] = ((result as any).discovered ?? [])\n .filter((d: any) => !!d.screenId && !!d.url)\n .map((d: any) => d.url as string);\n if (crawledUrls.length > 0) {\n (async () => {\n const cfg = await AppBrain.getCrawlConfig(tenantId, projectId);\n const existing: string[] = cfg?.seedUrls ?? [];\n const merged = Array.from(new Set([...existing, ...crawledUrls]));\n if (merged.length > existing.length) {\n await AppBrain.saveCrawlConfig(tenantId, projectId, { seedUrls: merged });\n logger.info({ added: merged.length - existing.length, total: merged.length }, '[crawl] auto-seeded crawled URLs into crawlConfig.seedUrls');\n }\n })().catch((err: any) => logger.warn({ err: err?.message }, '[crawl] auto-seed seedUrls failed (non-fatal)'));\n }\n\n notifyAdmins(tenantId, {\n type: 'crawl.done',\n title: 'Crawl completed',\n body: `Crawl for project finished successfully.`,\n metadata: { projectId, jobId },\n }).catch(console.error);\n\n dispatchWebhook(tenantId, projectId, 'crawl.done', {\n jobId,\n screensFound: (result as any)?.screensFound ?? (result as any)?.screens?.length ?? 0,\n }).catch(console.error);\n }\n\n // Aggregate token usage from LLM calls made during this job.\n // Delay 8s to let concurrent recordLLMCall writes settle before querying LLMCallLog.\n setTimeout(() => {\n aggregateJobTokens(tenantId, jobId, projectId, jobStartedAt).catch(console.error);\n }, 8000);\n\n // Post-crawl: generate / refresh all taxonomy skill files + investigate changes.\n setImmediate(async () => {\n try {\n const projectMap = await AppBrain.getProjectMap(tenantId, projectId);\n if (projectMap?.screens?.length) {\n // Mark project oversized when > 300 screens \u2014 activates VectorSearchGrounding\n const screenCount = (projectMap.screens as any[]).length;\n if (screenCount > 300) {\n await withTenant(tenantId, (tx) =>\n (tx as any).project.update({ where: { id: projectId }, data: { oversized: true } })\n );\n }\n await generateTaxonomySkillFiles(tenantId, projectId, projectMap.screens as any, result.apiEndpoints, result.performanceMetrics);\n\n // Auto-detect AppField catalog from crawled DOM snapshots.\n // Runs before test generation so getFieldsForCase() returns typed fields (FORM mode).\n // Capped at 20 screens and only where domSnapshotPath exists to bound LLM cost.\n try {\n const { readFile } = await import('node:fs/promises');\n const { existsSync } = await import('node:fs');\n const screensWithDom = (projectMap.screens as any[]).filter((s: any) => s.domSnapshotPath);\n const detectLimit = Math.min(screensWithDom.length, 20);\n let fieldDetectAdded = 0;\n for (let si = 0; si < detectLimit; si++) {\n const screen = screensWithDom[si];\n try {\n let htmlContent: string | null = null;\n const domPath: string = screen.domSnapshotPath;\n if (domPath.startsWith('https://') || domPath.startsWith('http://')) {\n const res = await fetch(domPath);\n if (res.ok) htmlContent = await res.text();\n } else if (existsSync(domPath)) {\n htmlContent = await readFile(domPath, 'utf8');\n }\n if (!htmlContent) continue;\n const detected = await detectFieldsFromHtml({ tenantId, projectId, html: htmlContent });\n if (detected.length > 0) {\n const merged = await mergeIntoCatalog({ tenantId, projectId, detected });\n fieldDetectAdded += merged.added;\n }\n } catch (sErr) {\n logger.warn({ screenId: screen.id, err: sErr }, '[field-detect] skipped screen');\n }\n }\n if (fieldDetectAdded > 0 || detectLimit > 0) {\n logger.info({ screens: detectLimit, added: fieldDetectAdded, projectId }, '[field-detect] catalog populated from crawl');\n // Refresh FIELD_CATALOG skill file after catalog is populated (GAP-SF-20)\n refreshFieldCatalogSkillFile(tenantId, projectId).catch(console.error);\n }\n } catch (fdErr) {\n logger.warn({ err: fdErr }, '[field-detect] post-crawl field detection failed');\n }\n\n // Cloud storage: upload screenshots to tenant S3/R2 if configured\n const tenantStorageCfg = await getTenantStorageConfig(tenantId).catch(() => null);\n if (tenantStorageCfg) {\n let uploaded = 0;\n for (const screen of projectMap.screens as any[]) {\n for (const shot of (screen.screenshots ?? []) as any[]) {\n if (!shot.storagePath || shot.cloudUrl) continue;\n try {\n const filename = `${screen.id}-v${shot.version}.png`;\n const key = buildArtifactKey(tenantId, projectId, 'screenshots', filename, tenantStorageCfg.prefix);\n const cloudUrl = await uploadScreenshotToTenantStorage(tenantStorageCfg, shot.storagePath, key);\n await AppBrain.updateScreenshotCloudUrl(tenantId, shot.id, cloudUrl);\n uploaded++;\n } catch (uploadErr) {\n logger.warn({ screenId: screen.id, err: uploadErr }, 'Failed to upload screenshot to cloud storage');\n }\n }\n }\n if (uploaded > 0) logger.info({ uploaded, provider: tenantStorageCfg.provider }, 'Uploaded screenshots to cloud storage');\n\n // Upload DOM snapshots and markdown to tenant storage\n const { existsSync } = await import('node:fs');\n let domUploaded = 0;\n let mdUploaded = 0;\n for (const screen of projectMap.screens as any[]) {\n // DOM snapshot\n const domPath = screen.domSnapshotPath;\n if (domPath && !domPath.startsWith('https://') && existsSync(domPath)) {\n try {\n const key = buildArtifactKey(tenantId, projectId, 'dom-snapshots', `${screen.id}.json`, tenantStorageCfg.prefix);\n const cloudUrl = await uploadFileToTenantStorage(tenantStorageCfg, domPath, key, 'application/json');\n await AppBrain.updateScreenDomSnapshot(tenantId, screen.id, domPath, cloudUrl);\n domUploaded++;\n } catch (err) {\n logger.warn({ screenId: screen.id, err }, 'Failed to upload DOM snapshot to cloud storage');\n }\n }\n // Markdown extract\n const mdPath = screen.markdownPath;\n if (mdPath && !mdPath.startsWith('https://') && existsSync(mdPath)) {\n try {\n const key = buildArtifactKey(tenantId, projectId, 'markdown', `${screen.id}.md`, tenantStorageCfg.prefix);\n const cloudUrl = await uploadFileToTenantStorage(tenantStorageCfg, mdPath, key, 'text/markdown');\n await AppBrain.updateScreenMarkdownPath(tenantId, screen.id, cloudUrl);\n mdUploaded++;\n } catch (err) {\n logger.warn({ screenId: screen.id, err }, 'Failed to upload markdown to cloud storage');\n }\n }\n }\n if (domUploaded > 0) logger.info({ domUploaded, provider: tenantStorageCfg.provider }, 'Uploaded DOM snapshots to cloud storage');\n if (mdUploaded > 0) logger.info({ mdUploaded, provider: tenantStorageCfg.provider }, 'Uploaded markdown to cloud storage');\n }\n\n // AI screen grouping \u2014 clusters screens into semantic feature areas\n await groupScreensWithAI(tenantId, projectId, projectMap.screens as any, jobId, jobStartedAt);\n\n // Auto-flag stale tests: mark PENDING_REVIEW when screen elements changed since test was generated\n try {\n await withTenant(tenantId, async (tx) => {\n const staleTests = await tx.$queryRaw<{ id: string }[]>`\n SELECT tc.id\n FROM \"TestCase\" tc\n JOIN \"Screen\" s ON tc.\"screenId\" = s.id\n WHERE tc.\"tenantId\" = ${tenantId}\n AND s.\"projectId\" = ${projectId}\n AND s.\"elementHashChangedAt\" IS NOT NULL\n AND tc.\"createdAt\" IS NOT NULL\n AND s.\"elementHashChangedAt\" > tc.\"createdAt\"\n AND tc.status != 'PENDING_REVIEW'\n AND tc.status != 'RETIRED'\n `;\n if (staleTests.length > 0) {\n const ids = staleTests.map((t: { id: string }) => t.id);\n await tx.testCase.updateMany({\n where: { id: { in: ids } },\n data: { status: 'PENDING_REVIEW' },\n });\n logger.info({ count: staleTests.length, projectId }, 'Auto-flagged stale test cases as PENDING_REVIEW');\n }\n });\n } catch (staleErr) {\n logger.warn({ err: staleErr }, 'Failed to auto-flag stale tests');\n }\n\n const changed = (projectMap.screens as any[]).filter((s: any) => s.hasChange);\n if (changed.length > 0) {\n await investigateScreenChanges(tenantId, projectId, changed);\n }\n }\n } catch (skillErr) {\n logger.error({ projectId, err: skillErr }, 'Taxonomy generation failed');\n }\n });\n\n } catch (err: any) {\n clearInterval(heartbeatTimer);\n runningControllers.delete(jobId);\n const aborted = String(err).includes('CRAWL_ABORTED');\n const authFailure = !aborted && (String(err).includes('AUTH_FAILURE') || String(err).includes('SESSION_EXPIRED'));\n const checkpoint = jobCheckpoints.get(jobId);\n jobCheckpoints.delete(jobId);\n\n // Auth failed mid-crawl with work still pending \u2014 auto-requeue continuation job.\n // The crawler checkpoints the remaining queue before throwing, so checkpoint.pendingQueue\n // holds URLs not yet visited. skipUrls prevents re-crawling already-done pages.\n if (authFailure && (checkpoint?.pendingQueue?.length ?? 0) > 0) {\n const pendingAfterAuth: string[] = checkpoint!.pendingQueue!;\n logger.info({ pending: pendingAfterAuth.length }, '[crawl] auth failure mid-crawl \u2014 auto-enqueuing continuation with pending URLs');\n enqueueJob('CRAWL', {\n ...config,\n startUrls: pendingAfterAuth,\n skipUrls: [...(config.skipUrls ?? []), ...(checkpoint!.visitedUrls ?? [])],\n }).catch((enqErr: any) => logger.warn({ err: enqErr?.message }, '[crawl] auth-failure auto-segment enqueue failed (non-fatal)'));\n }\n\n await recordJobFailure(tenantId, jobId, err, checkpoint, { isAborted: aborted });\n\n // Notify on crawl failure (fire-and-forget)\n if (!aborted) {\n notifyAdmins(tenantId, {\n type: 'crawl.failed',\n title: 'Crawl failed',\n body: `Crawl for project failed: ${String(err)}`,\n metadata: { projectId, jobId, error: String(err) },\n }).catch(console.error);\n\n // Dispatch outbound webhooks (fire-and-forget)\n dispatchWebhook(tenantId, projectId, 'crawl.failed', {\n jobId,\n error: String(err),\n }).catch(console.error);\n }\n } finally {\n runningControllers.delete(jobId);\n }\n}\n\n/** After a job finishes, sum LLMCallLog entries for that project during the job window. */\nexport async function aggregateJobTokens(tenantId: string, jobId: string, projectId: string, startedAt: Date) {\n try {\n await withTenant(tenantId, async (tx) => {\n const rows = await tx.$queryRaw<any[]>`\n SELECT\n COALESCE(SUM(\"promptTokens\"), 0)::INT AS \"totalPromptTokens\",\n COALESCE(SUM(\"completionTokens\"), 0)::INT AS \"totalCompletionTokens\",\n COALESCE(SUM(\"cacheReadTokens\"), 0)::INT AS \"totalCacheReadTokens\",\n COALESCE(SUM(\"estimatedCostUsd\"), 0.0) AS \"totalEstimatedCostUsd\",\n COUNT(*)::INT AS \"llmCallCount\"\n FROM \"LLMCallLog\"\n WHERE \"tenantId\" = ${tenantId}\n AND \"jobId\" = ${jobId}\n `;\n const row = rows[0];\n if (!row || !row.llmCallCount) return;\n await tx.platformJob.update({\n where: { id: jobId },\n data: {\n totalPromptTokens: row.totalPromptTokens,\n totalCompletionTokens: row.totalCompletionTokens,\n totalCacheReadTokens: row.totalCacheReadTokens,\n totalEstimatedCostUsd: Number(row.totalEstimatedCostUsd),\n llmCallCount: row.llmCallCount,\n },\n });\n });\n } catch (err) {\n console.warn('[job-tokens] aggregation failed:', err);\n }\n}\n\nasync function processApiTestsJob(jobId: string, config: any) {\n const { tenantId, projectId } = config;\n const jobStartedAt = new Date();\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: jobStartedAt } })\n );\n try {\n const { generateApiTests, refreshCoverageMatrix, refreshTestCaseInventory } = await import('@detiq/agents');\n await updateJobProgress(tenantId, jobId, 20, 'Loading API contracts and endpoints\u2026');\n const result = await generateApiTests({ tenantId, projectId, apiEndpoints: config.apiEndpoints });\n await updateJobProgress(tenantId, jobId, 90, `Generated ${result.cases?.length ?? 0} API test cases`);\n refreshCoverageMatrix(tenantId, projectId).catch((e) => console.warn('[coverage-matrix]', e));\n refreshTestCaseInventory(tenantId, projectId).catch((e) => console.warn('[tc-inventory]', e));\n refreshTestCasesSkillFile(tenantId, projectId).catch(console.error);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE', progress: 100, finishedAt: new Date(),\n result: { cases: result.cases?.length ?? 0, suiteName: result.suiteName } as any,\n },\n })\n );\n aggregateJobTokens(tenantId, jobId, projectId, jobStartedAt).catch(console.error);\n } catch (err: any) {\n await recordJobFailure(tenantId, jobId, err);\n }\n}\n\nasync function processSecurityTestsJob(jobId: string, config: any) {\n const { tenantId, projectId } = config;\n const jobStartedAt = new Date();\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: jobStartedAt } })\n );\n try {\n const { generateSecurityTests, refreshCoverageMatrix, refreshTestCaseInventory } = await import('@detiq/agents');\n await updateJobProgress(tenantId, jobId, 20, 'Mapping OWASP attack surface\u2026');\n const result = await generateSecurityTests({ tenantId, projectId, apiEndpoints: config.apiEndpoints });\n await updateJobProgress(tenantId, jobId, 90, `Generated ${result.cases?.length ?? 0} security test cases`);\n refreshCoverageMatrix(tenantId, projectId).catch((e) => console.warn('[coverage-matrix]', e));\n refreshTestCaseInventory(tenantId, projectId).catch((e) => console.warn('[tc-inventory]', e));\n refreshTestCasesSkillFile(tenantId, projectId).catch(console.error);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE', progress: 100, finishedAt: new Date(),\n result: { cases: result.cases?.length ?? 0, suiteName: result.suiteName } as any,\n },\n })\n );\n aggregateJobTokens(tenantId, jobId, projectId, jobStartedAt).catch(console.error);\n } catch (err: any) {\n await recordJobFailure(tenantId, jobId, err);\n }\n}\n\nasync function processIntegrationTestsJob(jobId: string, config: any) {\n const { tenantId, projectId } = config;\n const jobStartedAt = new Date();\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: jobStartedAt } })\n );\n try {\n const { generateIntegrationTests, refreshCoverageMatrix, refreshTestCaseInventory } = await import('@detiq/agents');\n await updateJobProgress(tenantId, jobId, 20, 'Analysing API contracts and data models\u2026');\n const result = await generateIntegrationTests({ tenantId, projectId, apiEndpoints: config.apiEndpoints });\n await updateJobProgress(tenantId, jobId, 90, `Generated ${result.cases?.length ?? 0} integration test cases`);\n refreshCoverageMatrix(tenantId, projectId).catch((e) => console.warn('[coverage-matrix]', e));\n refreshTestCaseInventory(tenantId, projectId).catch((e) => console.warn('[tc-inventory]', e));\n refreshTestCasesSkillFile(tenantId, projectId).catch(console.error);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE', progress: 100, finishedAt: new Date(),\n result: { cases: result.cases?.length ?? 0, suiteName: result.suiteName } as any,\n },\n })\n );\n aggregateJobTokens(tenantId, jobId, projectId, jobStartedAt).catch(console.error);\n } catch (err: any) {\n await recordJobFailure(tenantId, jobId, err);\n }\n}\n\nasync function processFigmaImportJob(jobId: string, config: any) {\n const { tenantId, projectId, fileKey } = config;\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: new Date() } })\n );\n try {\n // Prefer OAuth token (connected via Figma OAuth flow), fall back to PAT connector.\n const oauthConn = await AppBrain.getOAuthConnection(tenantId, projectId, 'FIGMA');\n let creds: figma.FigmaCredentials;\n const resolvedFileKey = fileKey ?? '';\n if (!resolvedFileKey) throw new Error('No Figma file key. Paste the full Figma file URL as the App URL.');\n\n if (oauthConn?.accessToken && oauthConn.status === 'CONNECTED') {\n // OAuth token must use Authorization: Bearer header (oauthToken field), not X-Figma-Token\n creds = { accessToken: '', oauthToken: oauthConn.accessToken, fileKey: resolvedFileKey };\n } else {\n const connector = await withTenant(tenantId, (tx) =>\n (tx as any).connector.findFirst({ where: { projectId, kind: 'FIGMA', tenantId } })\n ) as any;\n if (!connector) throw new Error('Figma connector not configured. Add your Figma token in Project Settings \u2192 Integrations.');\n const cfg = connector.config as Record<string, string>;\n const pat = cfg.accessToken_enc ? decrypt(cfg.accessToken_enc) : '';\n if (!pat) throw new Error('Figma access token missing. Edit the Figma connector and add your Personal Access Token.');\n creds = { accessToken: pat, fileKey: resolvedFileKey };\n }\n\n await updateJobProgress(tenantId, jobId, 5, 'Listing frames from Figma file\u2026');\n const frames = await figma.listScreens(creds, resolvedFileKey);\n if (frames.length === 0) {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', progress: 100, result: { screensImported: 0 } as any, finishedAt: new Date() } })\n );\n return;\n }\n\n await updateJobProgress(tenantId, jobId, 15, `Fetching images for ${frames.length} frames\u2026`);\n const nodeIds = frames.map((f: any) => f.id);\n const imageUrls = await figma.getScreenImages(creds, resolvedFileKey, nodeIds, 1);\n\n const screenshotDir = path.join(platformConfig.screenshotDir, projectId);\n await fs.mkdir(screenshotDir, { recursive: true });\n const tenantStorageCfg = await getTenantStorageConfig(tenantId).catch(() => null);\n\n let saved = 0;\n for (const frame of frames) {\n try {\n const pct = Math.round(15 + ((saved / frames.length) * 80));\n await updateJobProgress(tenantId, jobId, pct, `Saving frame ${saved + 1}/${frames.length}: ${(frame as any).name}`);\n const screen = await AppBrain.saveScreen(tenantId, projectId, (frame as any).name, `figma://file/${resolvedFileKey}/node/${(frame as any).id}`);\n const imgUrl = (imageUrls as any)[(frame as any).id];\n if (imgUrl) {\n const imgRes = await fetch(imgUrl);\n if (imgRes.ok) {\n const buf = Buffer.from(await imgRes.arrayBuffer());\n const filename = `${screen.id}-v1.png`;\n const storagePath = path.join(screenshotDir, filename);\n await fs.writeFile(storagePath, buf);\n let cloudUrl: string | undefined;\n if (tenantStorageCfg) {\n try {\n const key = buildArtifactKey(tenantId, projectId, 'screenshots', filename, tenantStorageCfg.prefix);\n cloudUrl = (await uploadScreenshotToTenantStorage(tenantStorageCfg, storagePath, key)) ?? undefined;\n } catch (e) {\n console.warn('[figma-import] cloud upload failed:', e);\n }\n }\n const w = (frame as any).boundingBox?.width ?? 1440;\n const viewport = w <= 480 ? 'MOBILE_PORTRAIT' : w <= 1024 ? 'TABLET' : 'DESKTOP';\n await AppBrain.saveScreenshot(tenantId, projectId, screen.id, storagePath, 1, viewport, cloudUrl);\n }\n }\n\n // Extract elements from Figma frame node \u2192 save for grounding + test generation\n if ((frame as any).node) {\n const figmaElements = figma.extractElements((frame as any).node);\n for (const el of figmaElements) {\n const role = el.type === 'COMPONENT' || el.type === 'COMPONENT_SET' ? 'COMPONENT'\n : el.type === 'INSTANCE' ? 'COMPONENT'\n : el.type === 'TEXT' ? 'TEXT'\n : el.type === 'FRAME' || el.type === 'GROUP' ? 'REGION'\n : el.type;\n const boundingRect = el.boundingBox\n ? { x: Math.round(el.boundingBox.x), y: Math.round(el.boundingBox.y), width: Math.round(el.boundingBox.width), height: Math.round(el.boundingBox.height) }\n : undefined;\n await AppBrain.saveElement(\n tenantId, projectId, screen.id,\n el.name, role, undefined,\n el.description ?? undefined, undefined,\n el.isInteractive,\n boundingRect, undefined, undefined,\n );\n }\n }\n\n saved++;\n } catch (err) {\n console.warn(`[figma-import] Failed to save frame:`, err);\n }\n }\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', progress: 100, result: { screensImported: saved, totalFrames: frames.length } as any, finishedAt: new Date() },\n })\n );\n\n // Post-import: run AI grouping + taxonomy skill files (same as post-crawl)\n setImmediate(async () => {\n try {\n const projectMap = await AppBrain.getProjectMap(tenantId, projectId);\n if (projectMap?.screens?.length) {\n await generateTaxonomySkillFiles(tenantId, projectId, projectMap.screens as any);\n await groupScreensWithAI(tenantId, projectId, projectMap.screens as any, jobId, new Date());\n }\n } catch (err) {\n console.error('[figma-import] post-import grouping failed:', err);\n }\n });\n\n } catch (err: any) {\n await recordJobFailure(tenantId, jobId, err);\n }\n}\n\nasync function processNativeCrawlJob(jobId: string, config: any) {\n const { tenantId, projectId } = config;\n const jobStartedAt = new Date();\n try {\n const build = await withTenant(tenantId, (tx) =>\n (tx as any).nativeAppBuild.findFirst({\n where: { tenantId, projectId },\n orderBy: { uploadedAt: 'desc' },\n })\n ) as { platform: string; fileFormat: string; s3Path: string; packageName: string | null } | null;\n\n if (!build) throw new Error('No native app build found for project');\n\n // AAB requires bundletool conversion \u2014 fail early with a clear message\n if (build.fileFormat === 'aab') {\n throw new Error(\n 'AAB format cannot be installed directly by Appium. Convert to APK using bundletool first, then re-upload the APK.'\n );\n }\n\n // Persist device targeting info on the job record\n const deviceFarm = (config.deviceFarm ?? 'local_emulator') as 'local_emulator' | 'aws_device_farm' | 'browserstack';\n await withTenant(tenantId, (tx) =>\n (tx as any).platformJob.update({\n where: { id: jobId },\n data: {\n deviceModel: config.deviceModel ?? null,\n osVersion: config.osVersion ?? null,\n deviceFarm,\n },\n })\n ).catch(() => { });\n\n const workspaceDeviceFarm = await AppBrain.getTenantDeviceFarmConfig(tenantId).catch(() => null);\n\n await runNativeCrawl({\n tenantId,\n projectId,\n jobId,\n platform: build.platform as 'android' | 'ios' | 'windows' | 'macos',\n appS3Path: build.s3Path,\n packageName: build.packageName ?? undefined,\n deviceModel: config.deviceModel,\n osVersion: config.osVersion,\n deviceFarm,\n appiumServerUrl: workspaceDeviceFarm?.appiumServerUrl,\n browserstackUsername: workspaceDeviceFarm?.browserstackUsername,\n browserstackAccessKey: workspaceDeviceFarm?.browserstackAccessKey,\n awsAccessKeyId: workspaceDeviceFarm?.awsAccessKeyId,\n awsSecretAccessKey: workspaceDeviceFarm?.awsSecretAccessKey,\n awsRegion: workspaceDeviceFarm?.awsRegion,\n awsDeviceFarmProjectArn: workspaceDeviceFarm?.awsDeviceFarmProjectArn,\n });\n\n // Post-crawl: generate taxonomy skill files + AI screen grouping (mirrors web crawl processJob)\n setImmediate(async () => {\n try {\n const projectMap = await AppBrain.getProjectMap(tenantId, projectId);\n const screens = (projectMap?.screens ?? []) as any[];\n if (screens.length > 0) {\n await generateTaxonomySkillFiles(tenantId, projectId, screens);\n await groupScreensWithAI(tenantId, projectId, screens, jobId, jobStartedAt);\n\n // Auto-flag stale tests: mark PENDING_REVIEW when screen elements changed since test was generated\n try {\n await withTenant(tenantId, async (tx) => {\n const staleTests = await tx.$queryRaw<{ id: string }[]>`\n SELECT tc.id\n FROM \"TestCase\" tc\n JOIN \"Screen\" s ON tc.\"screenId\" = s.id\n WHERE tc.\"tenantId\" = ${tenantId}\n AND s.\"projectId\" = ${projectId}\n AND s.\"elementHashChangedAt\" IS NOT NULL\n AND tc.\"createdAt\" IS NOT NULL\n AND s.\"elementHashChangedAt\" > tc.\"createdAt\"\n AND tc.status != 'PENDING_REVIEW'\n AND tc.status != 'RETIRED'\n `;\n if (staleTests.length > 0) {\n const ids = staleTests.map((t: { id: string }) => t.id);\n await tx.testCase.updateMany({\n where: { id: { in: ids } },\n data: { status: 'PENDING_REVIEW' },\n });\n logger.info({ count: staleTests.length, projectId }, 'Auto-flagged stale test cases as PENDING_REVIEW');\n }\n });\n } catch (staleErr) {\n logger.warn({ err: staleErr }, 'Failed to auto-flag stale tests');\n }\n }\n } catch (e) {\n logger.error({ err: e }, 'post-native-crawl processing failed');\n }\n });\n } catch (err: any) {\n await recordJobFailure(tenantId, jobId, err);\n }\n}\n\nasync function processGenerateTestsFromReqsJob(jobId: string, config: any) {\n const { tenantId, projectId, reqIds, suiteId, suiteName } = config;\n try {\n await updateJobProgress(tenantId, jobId, 5, 'Loading requirements');\n\n const { askTracked, deterministicSeed, getTaskAgentLLMConfig } = await import('@detiq/agents');\n const llmCfg = await getTaskAgentLLMConfig(tenantId, 'testGeneration');\n\n const all = await AppBrain.listRequirements(tenantId, projectId, {}) as any[];\n let reqs = reqIds?.length\n ? all.filter((r: any) => reqIds.includes(r.reqId))\n : all.filter((r: any) => r.status === 'unbuilt' || r.status === 'pending');\n\n if (reqs.length === 0) {\n await updateJobProgress(tenantId, jobId, 100, 'No pending requirements found');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', finishedAt: new Date(), result: { created: 0, skipped: 0 } as any } })\n );\n return;\n }\n\n // Resolve or create suite\n let resolvedSuiteId = suiteId;\n if (!resolvedSuiteId) {\n const name = suiteName?.trim() || 'Requirements Tests';\n const suite = await withTenant(tenantId, async (tx) => {\n const existing = await (tx as any).testSuite.findFirst({ where: { tenantId, projectId, name } });\n if (existing) return existing;\n return (tx as any).testSuite.create({ data: { tenantId, projectId, name } });\n }) as any;\n resolvedSuiteId = suite.id;\n }\n\n // Resume from checkpoint if present\n const existingJob = await prismaAdmin.platformJob.findUnique({\n where: { id: jobId },\n select: { checkpoint: true },\n }).catch(() => null);\n const completedReqIds = new Set<string>((existingJob?.checkpoint as any)?.completedReqIds ?? []);\n if (completedReqIds.size > 0) {\n reqs = reqs.filter((r: any) => !completedReqIds.has(r.reqId));\n }\n\n await updateJobProgress(tenantId, jobId, 20, `Generating tests for ${reqs.length} requirements`);\n\n const VALID_KINDS = new Set(['POSITIVE', 'NEGATIVE', 'EDGE', 'STATE_TRANSITION', 'ACCESSIBILITY', 'SECURITY', 'INTEGRATION', 'E2E', 'API', 'EXPLORATORY', 'UAT', 'PERFORMANCE', 'COMPATIBILITY', 'L10N', 'USABILITY', 'LOAD', 'UNIT', 'MUTATION', 'AB_TEST', 'ALPHA_BETA', 'SYSTEM', 'SMOKE', 'REGRESSION', 'SANITY']);\n const VALID_PRIORITIES = new Set(['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']);\n\n // Process requirements in chunks of 5 with checkpointing\n const CHUNK_SIZE = 5;\n let totalCreated = (existingJob?.checkpoint as any)?.createdCount ?? 0;\n let totalSkipped = (existingJob?.checkpoint as any)?.skippedCount ?? 0;\n const finishedReqIds: string[] = Array.from(completedReqIds);\n\n for (let i = 0; i < reqs.length; i += CHUNK_SIZE) {\n const chunk = reqs.slice(i, i + CHUNK_SIZE);\n const reqBlock = chunk.map((r: any) => [\n `## ${r.reqId}: ${r.title}`,\n `Priority: ${r.priority} | Type: ${r.type}`,\n r.acceptanceCriteria?.length ? `Acceptance Criteria:\\n${r.acceptanceCriteria.map((c: string) => `- ${c}`).join('\\n')}` : '',\n r.businessRules?.length ? `Business Rules:\\n${r.businessRules.map((b: string) => `- ${b}`).join('\\n')}` : '',\n ].filter(Boolean).join('\\n')).join('\\n\\n');\n\n const system = `You are ZeTa-AI's test case generator. Convert requirements into structured test cases.\nReturn ONLY valid JSON: { \"cases\": [ { \"title\": string, \"kind\": string, \"priority\": string, \"preconditions\": string, \"steps\": [{\"action\": string, \"data\": string, \"expected\": string}], \"expectedResult\": string, \"tags\": string[], \"reqRef\": string } ] }\nKind must be one of: POSITIVE, NEGATIVE, EDGE, SECURITY, ACCESSIBILITY, INTEGRATION, E2E, SMOKE, REGRESSION.\nPriority must be one of: CRITICAL, HIGH, MEDIUM, LOW.\nGenerate 1-3 test cases per requirement (positive + negative + edge if applicable).\nEach test case must reference its reqRef (the requirement ID like REQ-001).`;\n\n const user = `Generate test cases for these requirements:\\n\\n${reqBlock}`;\n\n let parsed: { cases: any[] } = { cases: [] };\n try {\n const reqSeed = deterministicSeed(`req-cases:${chunk.map((r: any) => r.reqId).sort().join('|').slice(0, 300)}`);\n const { text, usage } = await askTracked(system, user, llmCfg, undefined, undefined, { temperature: 0, seed: reqSeed });\n if (usage) recordLLMCall({ tenantId, projectId, jobId, agentType: 'generate_tests_from_reqs', label: 'test_case_generation', usage }).catch(() => { });\n const json = text.match(/\\{[\\s\\S]*\\}/)?.[0];\n if (json) parsed = JSON.parse(json);\n } catch (e: any) {\n throw new Error('AI failed to generate test cases: ' + (e.message ?? 'unknown'));\n }\n\n const normalized = (parsed.cases ?? []).map((c: any) => ({\n ref: c.reqRef ?? null,\n traceTo: c.reqRef ?? null,\n title: c.title || 'Untitled',\n kind: VALID_KINDS.has((c.kind ?? '').toUpperCase()) ? c.kind.toUpperCase() : 'POSITIVE',\n priority: VALID_PRIORITIES.has((c.priority ?? '').toUpperCase()) ? c.priority.toUpperCase() : 'MEDIUM',\n preconditions: c.preconditions ?? '',\n steps: Array.isArray(c.steps) ? c.steps : [],\n expectedResult: c.expectedResult ?? '',\n tags: Array.isArray(c.tags) ? c.tags : [],\n }));\n\n const { created, skipped } = await AppBrain.importTestCasesFromArtifact(\n tenantId, projectId, resolvedSuiteId, normalized, { duplicateMode: 'skip' }\n );\n totalCreated += created.length;\n totalSkipped += (skipped ?? 0);\n\n // Mark processed requirements as covered\n const coveredReqIds = new Set(normalized.map((c: any) => c.ref).filter(Boolean));\n await Promise.all(\n chunk\n .filter((r: any) => coveredReqIds.has(r.reqId))\n .map((r: any) => AppBrain.updateRequirement(tenantId, projectId, r.reqId, { status: 'covered' }).catch(() => { }))\n );\n\n for (const r of chunk) finishedReqIds.push(r.reqId);\n\n // Save checkpoint after chunk\n await prismaAdmin.platformJob.update({\n where: { id: jobId },\n data: {\n checkpoint: { completedReqIds: finishedReqIds, createdCount: totalCreated, skippedCount: totalSkipped } as any,\n checkpointAt: new Date(),\n },\n }).catch(() => {});\n\n const progressPct = Math.round(20 + ((i + chunk.length) / reqs.length) * 75);\n await updateJobProgress(tenantId, jobId, progressPct, `Processed ${finishedReqIds.length}/${reqs.length} requirements (${totalCreated} cases generated)`);\n }\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', finishedAt: new Date(), result: { created: totalCreated, skipped: totalSkipped, suiteId: resolvedSuiteId } as any, progress: 100 },\n })\n );\n } catch (e: any) {\n const msg = e?.message ?? 'Unknown error';\n logger.error({ jobId, err: msg }, 'processGenerateTestsFromReqsJob failed');\n await recordJobFailure(tenantId, jobId, e);\n }\n}\n\n/**\n * Runs every APPROVED test case in a project (optionally scoped to one suite)\n * against the agent-free Playwright executor (test-executor.ts) and aggregates\n * the results into the TestRun row created synchronously by POST /run-tests\n * (see ci.ts) \u2014 this is what makes the CI gate (GET /test-results/latest)\n * finally reflect what CI itself triggered, instead of a stale or nonexistent\n * result from an unrelated, manually-triggered, agent-connected suite run.\n */\nasync function processRunTestSuiteJob(jobId: string, config: any) {\n const { tenantId, projectId, testRunId, suiteId, storageState, browser, shardIndex, totalShards } = config as any;\n const runStart = Date.now();\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: new Date() } })\n );\n\n try {\n let testCases = await AppBrain.listTestCases(tenantId, projectId, {\n status: 'APPROVED',\n ...(suiteId ? { suiteId } : {}),\n }) as Array<{ id: string }>;\n\n // Shard filtering: when running a distributed matrix (CI-triggered multi-browser/shard),\n // each worker only executes its own slice \u2014 testCases[shardIndex::totalShards].\n if (typeof totalShards === 'number' && totalShards > 1 && typeof shardIndex === 'number') {\n testCases = testCases.filter((_, i) => i % totalShards === shardIndex);\n }\n\n if (testCases.length === 0) {\n // Nothing approved to run isn't a CI failure \u2014 same as any test runner\n // reporting \"0 tests, 0 failures\" when a suite is empty.\n await AppBrain.completeTestRun(tenantId, testRunId, { total: 0, passed: 0, failed: 0, skipped: 0, duration: 0 });\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', progress: 100, finishedAt: new Date(), result: { total: 0 } as any },\n })\n );\n return;\n }\n\n const aiHealingEnabled = await isFeatureEnabled(tenantId, 'AI_SELF_HEALING');\n const SCREENSHOT_DIR = process.env.SCREENSHOT_DIR ?? '/tmp/zeta-screenshots';\n const screenshotDir = config.screenshotDir || platformConfig.screenshotDir || SCREENSHOT_DIR;\n const VALID_BROWSERS = ['chromium', 'firefox', 'webkit'];\n const validBrowser = VALID_BROWSERS.includes(browser) ? browser : 'chromium';\n\n let passed = 0;\n let failed = 0;\n const runResults: Array<{ testCaseId: string; status: string }> = [];\n\n // Resume from checkpoint if present\n const existingJob = await prismaAdmin.platformJob.findUnique({\n where: { id: jobId },\n select: { checkpoint: true },\n }).catch(() => null);\n const completedCaseIds = new Set<string>((existingJob?.checkpoint as any)?.completedCaseIds ?? []);\n if (completedCaseIds.size > 0) {\n testCases = testCases.filter((tc) => !completedCaseIds.has(tc.id));\n passed = (existingJob?.checkpoint as any)?.passed ?? 0;\n failed = (existingJob?.checkpoint as any)?.failed ?? 0;\n }\n\n const allFinishedCaseIds: string[] = Array.from(completedCaseIds);\n\n for (let i = 0; i < testCases.length; i += TEST_EXEC_CONCURRENCY) {\n const batch = testCases.slice(i, i + TEST_EXEC_CONCURRENCY);\n await Promise.all(batch.map(async (tc) => {\n try {\n const full = await AppBrain.getTestCaseForExecution(tenantId, tc.id) as any;\n const baseUrl = full?.screen?.project?.appUrl ?? '';\n const execProjectId = full?.screen?.project?.id ?? projectId;\n const dataRows: Array<{ id: string; rowIndex: number; data: Record<string, string> }> =\n full?.dataSet?.rows ?? [];\n\n // Data-driven: run once per row (substituting placeholders). If no data set, run once with no substitution.\n const runs = dataRows.length > 0 ? dataRows : [null];\n\n for (const row of runs) {\n let tcForPlan = full ?? tc;\n if (row !== null) {\n // Clone the test case with interpolated steps and title\n const rawSteps = Array.isArray(tcForPlan.steps) ? tcForPlan.steps as string[] : [];\n tcForPlan = {\n ...tcForPlan,\n steps: rawSteps.map((s: string) => interpolatePlaceholders(s, row.data)),\n title: interpolatePlaceholders(tcForPlan.title ?? '', row.data),\n preconditions: tcForPlan.preconditions\n ? interpolatePlaceholders(tcForPlan.preconditions, row.data)\n : tcForPlan.preconditions,\n };\n }\n\n const plan = await generateExecutionPlan(tenantId, tcForPlan, baseUrl);\n const result = await executeTestPlan(plan, {\n screenshotDir,\n testRunId,\n maxRuns: 1,\n browser: validBrowser as 'chromium' | 'firefox' | 'webkit',\n storageState,\n healCtx: aiHealingEnabled ? { tenantId, projectId: execProjectId } : undefined,\n });\n const status = result.status === 'PASSED' ? 'PASSED' : result.status === 'ERROR' ? 'ERROR' : 'FAILED';\n if (status === 'PASSED') passed++; else failed++;\n runResults.push({ testCaseId: tc.id, status });\n await AppBrain.saveTestResult(tenantId, {\n runId: testRunId,\n testCaseId: tc.id,\n status,\n duration: result.totalDuration,\n ...(result.rootCauseSummary\n ? { failureAnalysis: result.rootCauseSummary, failureAnalysisAt: new Date() }\n : {}),\n ...(row !== null ? { dataRowId: row.id, dataRowIndex: row.rowIndex } : {}),\n ...((result.consoleLogs?.length || result.networkErrors?.length) ? {\n logs: {\n consoleLogs: result.consoleLogs ?? [],\n networkErrors: result.networkErrors ?? [],\n },\n } : {}),\n });\n }\n await AppBrain.recordFlakinessOutcome(tenantId, tc.id).catch(() => { });\n } catch (err: any) {\n failed++;\n runResults.push({ testCaseId: tc.id, status: 'ERROR' });\n await AppBrain.saveTestResult(tenantId, {\n runId: testRunId,\n testCaseId: tc.id,\n status: 'ERROR',\n error: String(err?.message ?? err).slice(0, 500),\n }).catch(() => { });\n }\n }));\n\n for (const tc of batch) allFinishedCaseIds.push(tc.id);\n\n // Save durable checkpoint after each batch\n await prismaAdmin.platformJob.update({\n where: { id: jobId },\n data: {\n checkpoint: { completedCaseIds: allFinishedCaseIds, passed, failed } as any,\n checkpointAt: new Date(),\n },\n }).catch(() => {});\n\n await updateJobProgress(\n tenantId, jobId,\n Math.round((Math.min(i + TEST_EXEC_CONCURRENCY, testCases.length) / testCases.length) * 100),\n `Ran ${Math.min(i + TEST_EXEC_CONCURRENCY, testCases.length)}/${testCases.length} test cases`,\n );\n }\n\n await AppBrain.completeTestRun(tenantId, testRunId, {\n total: testCases.length,\n passed,\n failed,\n skipped: 0,\n duration: Date.now() - runStart,\n });\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', progress: 100, finishedAt: new Date(), result: { total: testCases.length, passed, failed } as any },\n })\n );\n\n // Same full fan-out as the interactive web-driven run path (in-app,\n // generic webhook, suite-owner failure emails, GitHub check-run, Slack,\n // Teams, alert emails on failure) \u2014 a CI-triggered run has nobody\n // watching the SSE stream, so this is the only place these fire from.\n notifyRunCompletion(\n tenantId, projectId, testRunId,\n failed === 0 ? 'COMPLETED' : 'FAILED',\n { passed, failed, total: testCases.length },\n runResults,\n ).catch(console.error);\n refreshTestExecutionHistorySkillFile(tenantId, projectId).catch(console.error);\n } catch (err: any) {\n const msg = String(err?.message ?? err).slice(0, 500);\n logger.error({ jobId, testRunId, err: msg }, 'processRunTestSuiteJob failed');\n await AppBrain.failTestRun(tenantId, testRunId, msg).catch(() => { });\n await recordJobFailure(tenantId, jobId, err);\n }\n}\n\nasync function processGenerateSkillFilesJob(jobId: string, config: any) {\n const { tenantId, projectId } = config;\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: new Date() } })\n );\n try {\n await updateJobProgress(tenantId, jobId, 10, 'Loading project data\u2026');\n const projectMap = await AppBrain.getProjectMap(tenantId, projectId);\n if (!projectMap) throw new Error('Project not found.');\n const screens = (projectMap.screens ?? []) as any[];\n // Manual regeneration has no fresh crawl result \u2014 reuse the metrics and\n // endpoints persisted on the last completed crawl job so the Performance\n // Baseline and API Contract aren't wiped to \"no data captured\".\n const lastCrawl = await withTenant(tenantId, (tx) =>\n tx.platformJob.findFirst({\n where: { projectId, type: 'CRAWL', status: 'DONE' },\n orderBy: { finishedAt: 'desc' },\n select: { result: true },\n })\n );\n const lastResult = ((lastCrawl as any)?.result ?? {}) as any;\n await updateJobProgress(tenantId, jobId, 25, `Generating ${screens.length} app intelligence skill files\u2026`);\n await generateTaxonomySkillFiles(tenantId, projectId, screens, lastResult.apiEndpoints, lastResult.performanceMetrics);\n await updateJobProgress(tenantId, jobId, 85, 'Refreshing testing intelligence skill files\u2026');\n await Promise.allSettled([\n refreshTestCasesSkillFile(tenantId, projectId),\n refreshTestDataSkillFile(tenantId, projectId),\n refreshTestScriptSkillFile(tenantId, projectId),\n refreshRequirementsCoverageSkillFile(tenantId, projectId),\n refreshTestExecutionHistorySkillFile(tenantId, projectId),\n refreshVisualRegressionHistorySkillFile(tenantId, projectId),\n refreshLocatorStrategyWithHealingData(tenantId, projectId),\n refreshFieldCatalogSkillFile(tenantId, projectId),\n ]);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', progress: 100, finishedAt: new Date() } })\n );\n } catch (err: any) {\n await recordJobFailure(tenantId, jobId, err);\n }\n}\n\n// DATA_RETENTION_PURGE \u2014 deletes AuditLog rows older than each tenant's\n// dataRetentionDays setting (default 365), and LLMCallLog rows older than 90\n// days (fixed, no per-tenant override). Runs once daily via the cron in\n// worker.ts. The job has no tenantId/projectId \u2014 it sweeps all tenants so\n// enqueueJob() is not used; the worker.ts cron calls this directly.\nexport async function processDataRetentionPurge(): Promise<void> {\n logger.info('[data-retention] starting purge run');\n\n // LLMCallLog: fixed 90-day retention across all tenants (uses calledAt, not createdAt)\n const llmCutoff = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);\n const llmResult = await prismaAdmin.lLMCallLog.deleteMany({\n where: { calledAt: { lt: llmCutoff } },\n });\n logger.info({ deleted: llmResult.count }, '[data-retention] LLMCallLog purged');\n\n // AuditLog: per-tenant retention via TenantSetting.dataRetentionDays\n const tenants = await prismaAdmin.tenant.findMany({ select: { id: true } });\n let auditTotal = 0;\n\n for (const { id: tenantId } of tenants) {\n // Find the most restrictive (lowest) dataRetentionDays across this tenant's\n // settings rows, falling back to 365 if no setting row exists.\n const setting = await prismaAdmin.tenantSetting.findFirst({\n where: { tenantId },\n orderBy: { dataRetentionDays: 'asc' },\n select: { dataRetentionDays: true },\n });\n const retentionDays = setting?.dataRetentionDays ?? (parseInt(process.env.DEFAULT_DATA_RETENTION_DAYS ?? '') || 365);\n const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);\n\n const result = await prismaAdmin.auditLog.deleteMany({\n where: { tenantId, createdAt: { lt: cutoff } },\n });\n auditTotal += result.count;\n }\n\n logger.info({ deleted: auditTotal, tenants: tenants.length }, '[data-retention] AuditLog purged');\n\n // AuthSession: fixed 12-month retention \u2014 IP addresses are PII under GDPR.\n // Only purge revoked sessions; active (non-revoked) sessions are kept.\n const SESSION_RETENTION_DAYS = 365;\n const sessionCutoff = new Date(Date.now() - SESSION_RETENTION_DAYS * 24 * 60 * 60_000);\n const sessionResult = await prismaAdmin.authSession.deleteMany({\n where: {\n createdAt: { lt: sessionCutoff },\n revokedAt: { not: null },\n },\n });\n logger.info({ deleted: sessionResult.count }, '[data-retention] AuthSession purged');\n\n // PlatformJob: delete completed jobs older than 90 days (GAP-24)\n const ninetyDaysAgo = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);\n const deletedJobs = await prismaAdmin.platformJob.deleteMany({\n where: {\n status: { in: ['DONE', 'FAILED'] },\n dlq: false,\n inline: false,\n finishedAt: { lt: ninetyDaysAgo },\n },\n });\n if (deletedJobs.count > 0) {\n logger.info({ count: deletedJobs.count }, '[data-retention] deleted old completed jobs');\n }\n}\n\n// \u2500\u2500 URL Discovery Job \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Three-phase URL discovery: sitemap seed \u2192 unauthenticated BFS spider \u2192 authenticated BFS\n// spider (when crawl credentials exist). Persists discovered URLs to crawlConfig and marks the\n// job DONE with the full URL list so the UI can poll for results.\n\nasync function processDiscoverJob(jobId: string, config: any, opts?: ProcessJobOpts) {\n const { tenantId, projectId } = config;\n\n const controller = new AbortController();\n runningControllers.set(jobId, controller);\n\n // Independent heartbeat timer \u2014 keeps heartbeatAt fresh even when no URLs are\n // arriving (e.g. during auth login, page-load waits, 2FA handling).\n const heartbeatTimer = setInterval(() => {\n void heartbeatJob(tenantId, jobId);\n }, JOB_HEARTBEAT_INTERVAL_MS);\n\n // Shared accumulator \u2014 updated by onUrlsDiscovered callback as each page is visited.\n // This lets the frontend poll and see a growing list before the job finishes.\n const allUrls = new Set<string>();\n let lastPartialFlush = 0;\n let lastFlushedPct = 0; // high-water mark \u2014 progress never decreases\n\n const flushPartialUrls = async (pct: number, step: string) => {\n const now = Date.now();\n // Flush to DB at most every 5 seconds to avoid excessive writes\n if (now - lastPartialFlush < 5_000 && allUrls.size < 5) return;\n const monotonic = Math.max(pct, lastFlushedPct);\n lastFlushedPct = monotonic;\n lastPartialFlush = now;\n try {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { progress: monotonic, result: { step, partialUrls: Array.from(allUrls) } as any, heartbeatAt: new Date() },\n })\n );\n } catch { /* non-fatal */ }\n await opts?.onProgress?.(monotonic);\n };\n\n const onProgress = async (pct: number, step: string) => {\n await updateJobProgress(tenantId, jobId, pct, step);\n await opts?.onProgress?.(pct);\n };\n\n try {\n // Mark RUNNING inside the try block so any DB error is caught by the internal\n // handler below (which writes result.error). If this were before the try block,\n // a DB failure would bubble up to dispatchClaimedJob's catch, which writes to\n // the `error` column instead of `result.error` \u2014 the frontend would then show\n // \"Unknown error\" rather than the actual message.\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: new Date() } })\n );\n // Load decrypted project crawl config for credentials/proxy/sitemap settings.\n // Reading Project.crawlConfig directly leaves encrypted fields as *_enc, so\n // discovery would see username but miss password/storageState/authToken.\n const crawlCfg = await AppBrain.getCrawlConfig(tenantId, projectId).catch(() => null) ?? {};\n const appUrl: string = config.appUrl ?? crawlCfg.appUrl;\n if (!appUrl) throw new Error('No app URL configured. Save the crawl settings first.');\n\n const { getSystemConfig } = await import('@detiq/core');\n const crawlTuning = await getSystemConfig('crawl_tuning');\n const maxDiscoverUrls: number = config.maxDiscoverUrls ?? parseInt(String(crawlTuning?.maxDiscoverUrls ?? '2000'), 10);\n const proxyUrl: string | undefined = config.proxyUrl ?? crawlCfg.proxyUrl;\n const screenshotDir = os.tmpdir();\n const discoverStorageConfig = await getTenantStorageConfig(tenantId).catch(() => null);\n\n await onProgress(3, 'Seeding from sitemap/robots.txt\u2026');\n\n // Phase 1 \u2014 sitemap + robots.txt seed (fast, no browser)\n const sitemapResult = await discoverProjectUrls(appUrl, {\n sitemapUrl: config.sitemapUrl ?? crawlCfg.sitemapUrl,\n proxyUrl,\n }).catch(() => ({ candidates: [] as Array<{ url: string; source: string }>, sitemapFound: false }));\n const sitemapUrls = sitemapResult.candidates.map((c: any) => c.url as string);\n // Seed accumulator with sitemap URLs so they show immediately\n for (const u of sitemapUrls) allUrls.add(u);\n logger.info({ count: sitemapUrls.length, sitemapFound: sitemapResult.sitemapFound }, '[discover] Phase 1 sitemap done');\n\n // Phase 2 \u2014 unauthenticated BFS spider\n await onProgress(8, `Unauthenticated spider from ${appUrl}\u2026`);\n const unauthResult = await crawlProject({\n tenantId,\n projectId,\n appUrl,\n discoveryMode: true,\n startUrls: sitemapUrls.length > 0 ? sitemapUrls : undefined,\n screenshotDir,\n maxScreens: maxDiscoverUrls,\n proxyUrl,\n useFirefox: crawlCfg.useFirefox,\n storageConfig: discoverStorageConfig,\n abortSignal: controller.signal,\n jobId,\n onUrlsDiscovered: async (urls: string[]) => {\n for (const u of urls) allUrls.add(u);\n const pct = 8 + Math.round((allUrls.size / Math.max(1, maxDiscoverUrls)) * 42);\n await flushPartialUrls(Math.min(pct, 49), `Discovered ${allUrls.size} URLs\u2026`);\n },\n }, async (pct: number, step: string) => {\n await flushPartialUrls(8 + Math.round(pct * 0.42), `Unauthenticated: ${step}`);\n }).catch((err: any) => {\n if (err?.message === 'CRAWL_ABORTED') throw err;\n logger.warn({ err: err?.message }, '[discover] Phase 2 unauthenticated spider failed \u2014 continuing with partial results');\n return { discoveredUrls: [] as string[] };\n });\n\n for (const u of ((unauthResult as any).discoveredUrls ?? [] as string[])) allUrls.add(u);\n logger.info({ count: allUrls.size }, '[discover] Phase 2 unauth spider done');\n\n // Phase 3 \u2014 authenticated BFS spider (only when credentials configured)\n // getCrawlConfig returns flat fields (username/password/totpSecret) \u2014 NOT nested under .credentials\n let resolvedSso: {\n ssoProvider?: string;\n ssoDomain?: string;\n ssoClientId?: string;\n ssoClientSecret?: string;\n ssoScope?: string;\n } = {};\n if (crawlCfg.ssoProviderId) {\n const ssoProviders = await AppBrain.getWorkspaceSsoProviders(tenantId).catch(() => []);\n const ssoProvider = ssoProviders.find((p: any) => p.id === crawlCfg.ssoProviderId);\n if (ssoProvider) {\n resolvedSso = {\n ssoProvider: ssoProvider.provider,\n ssoDomain: ssoProvider.domain,\n ssoClientId: ssoProvider.clientId,\n ssoClientSecret: ssoProvider.clientSecret,\n ssoScope: ssoProvider.scope,\n };\n }\n }\n\n const hasCreds = !!(\n crawlCfg.username ||\n crawlCfg.storageState ||\n crawlCfg.authToken ||\n crawlCfg.chromeProfilePath ||\n (crawlCfg.httpBasicUsername && crawlCfg.httpBasicPassword)\n );\n if (hasCreds && !controller.signal.aborted) {\n await onProgress(52, 'Authenticated spider\u2026');\n const runtimeCreds = crawlCfg.username\n ? {\n username: crawlCfg.username,\n password: crawlCfg.password ?? '',\n ...(crawlCfg.totpSecret\n ? { getOneTimeCode: async () => generateTOTP(crawlCfg.totpSecret!) }\n : {}),\n }\n : undefined;\n\n const PHASE3_TIMEOUT_MS = 20 * 60_000; // 20-minute cap on authenticated spider\n const authResult = await Promise.race([\n crawlProject({\n tenantId,\n projectId,\n appUrl,\n discoveryMode: true,\n credentials: runtimeCreds,\n storageState: crawlCfg.storageState,\n httpBasicUsername: crawlCfg.httpBasicUsername,\n httpBasicPassword: crawlCfg.httpBasicPassword,\n authToken: crawlCfg.authToken,\n authTokenHeader: crawlCfg.authTokenHeader,\n authTokenPrefix: crawlCfg.authTokenPrefix,\n startUrls: crawlCfg.postLoginUrl\n ? [crawlCfg.postLoginUrl, ...(sitemapUrls.length > 0 ? sitemapUrls : [])]\n : (sitemapUrls.length > 0 ? sitemapUrls : undefined),\n screenshotDir,\n maxScreens: maxDiscoverUrls,\n proxyUrl,\n chromeProfilePath: crawlCfg.chromeProfilePath,\n useFirefox: crawlCfg.useFirefox,\n storageConfig: discoverStorageConfig,\n captchaSolverApiKey: crawlCfg.captchaSolverApiKey,\n captchaSolverProvider: crawlCfg.captchaSolverProvider,\n mailslurpApiKey: crawlCfg.mailslurpApiKey,\n mailslurpInboxId: crawlCfg.mailslurpInboxId,\n loginInstructions: crawlCfg.loginInstructions,\n ...resolvedSso,\n abortSignal: controller.signal,\n jobId,\n onUrlsDiscovered: async (urls: string[]) => {\n for (const u of urls) allUrls.add(u);\n const pct = 52 + Math.round((allUrls.size / Math.max(1, maxDiscoverUrls)) * 38);\n await flushPartialUrls(Math.min(pct, 89), `Discovered ${allUrls.size} URLs (authenticated)\u2026`);\n },\n }, async (pct: number, step: string) => {\n await flushPartialUrls(52 + Math.round(pct * 0.38), `Authenticated: ${step}`);\n }),\n new Promise<never>((_, rej) => setTimeout(() => rej(new Error('AUTH_PHASE_TIMEOUT')), PHASE3_TIMEOUT_MS)),\n ]).catch((err: any) => {\n if (err?.message === 'CRAWL_ABORTED') throw err;\n const reason = err?.message === 'AUTH_PHASE_TIMEOUT'\n ? 'Authenticated spider timed out after 20 minutes'\n : err?.message;\n logger.warn({ reason }, '[discover] Phase 3 authenticated spider failed \u2014 using unauthenticated results');\n return { discoveredUrls: [] as string[] };\n });\n\n for (const u of (((authResult as any).discoveredUrls) ?? [] as string[])) {\n allUrls.add(u);\n }\n logger.info({ count: allUrls.size }, '[discover] Phase 3 auth spider done');\n }\n\n // Persist discovered URLs to project crawl config\n // Filter sentinel strings (_pending_, _auth_wall_, etc.) that may have leaked in\n // via onUrlsDiscovered callbacks fired before visited-map entries were updated.\n const discoveredUrls = Array.from(allUrls).filter(u => u.startsWith('http'));\n const existingCfg = await AppBrain.getCrawlConfig(tenantId, projectId).catch(() => ({}));\n await AppBrain.saveCrawlConfig(tenantId, projectId, {\n ...(existingCfg as any),\n discoveredUrls,\n discoveredAt: new Date().toISOString(),\n });\n\n await onProgress(100, `Done \u2014 ${discoveredUrls.length} URLs discovered`);\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE',\n result: {\n discoveredUrls,\n count: discoveredUrls.length,\n sitemapFound: sitemapResult.sitemapFound,\n } as any,\n progress: 100,\n finishedAt: new Date(),\n },\n })\n );\n\n notifyAdmins(tenantId, {\n type: 'discovery.done',\n title: 'URL Discovery completed',\n body: `Found ${discoveredUrls.length} URLs for the project.`,\n metadata: { projectId, jobId },\n }).catch(console.error);\n\n } catch (err: any) {\n logger.error({ err: err?.message, jobId }, '[discover] Job failed');\n await recordJobFailure(tenantId, jobId, err);\n } finally {\n clearInterval(heartbeatTimer);\n runningControllers.delete(jobId);\n }\n}\n\n// \u2500\u2500 Site Monitor Job \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Lightweight scheduled content check: fetches configured URLs, diffs content\n// hashes against stored baselines, fires content.changed webhooks on drift.\n// Baselines are stored in project.crawlConfig.monitorBaselines (url\u2192hash map).\n\nasync function processMonitorJob(jobId: string, config: any, opts?: ProcessJobOpts) {\n const { tenantId, projectId } = config;\n const controller = new AbortController();\n runningControllers.set(jobId, controller);\n\n try {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: new Date() } })\n );\n\n const proj: any = await withTenant(tenantId, (tx) =>\n (tx as any).project.findUnique({ where: { id: projectId }, select: { crawlConfig: true } })\n );\n const crawlCfg = proj?.crawlConfig ?? {};\n const monitorUrls: string[] = config.monitorUrls ?? crawlCfg.monitorUrls ?? [];\n\n if (monitorUrls.length === 0) {\n throw new Error('No monitor URLs configured. Add monitorUrls to crawl config or job config.');\n }\n\n await updateJobProgress(tenantId, jobId, 10, `Monitoring ${monitorUrls.length} URL(s)...`);\n\n // Baseline: stored baselines from previous monitor run take priority;\n // project screens' elementHash is the fallback for first-run.\n const storedBaselines: Record<string, string> = crawlCfg.monitorBaselines ?? {};\n const projectScreens: Array<{ url: string | null; elementHash: string | null }> =\n await withTenant(tenantId, (tx) =>\n tx.screen.findMany({ where: { projectId }, select: { url: true, elementHash: true } })\n ).catch(() => []) as any;\n const screenHashMap: Record<string, string> = {};\n for (const s of projectScreens) {\n if (s.url && s.elementHash) screenHashMap[s.url] = s.elementHash;\n }\n\n const results: Array<{\n url: string; changed: boolean; reason: string;\n currentHash: string; previousHash: string | null;\n }> = [];\n const newBaselines: Record<string, string> = { ...storedBaselines };\n\n for (let i = 0; i < monitorUrls.length; i++) {\n if (controller.signal.aborted) break;\n const url = monitorUrls[i];\n const pct = 10 + Math.floor((i / monitorUrls.length) * 80);\n await updateJobProgress(tenantId, jobId, pct, `Checking ${url}...`);\n opts?.onProgress?.(pct);\n\n try {\n const crawlResult = await httpCrawl({ appUrl: url, maxScreens: 1, respectRobotsTxt: false, timeoutMs: parseInt(process.env.CRAWL_TIMEOUT_MS ?? '20000', 10) });\n const screen = crawlResult.screens[0];\n if (!screen) {\n results.push({ url, changed: false, reason: 'fetch_empty', currentHash: '', previousHash: null });\n continue;\n }\n const currentHash = hashContent(normalizeText(screen.markdown));\n const previousHash = storedBaselines[url] ?? screenHashMap[url] ?? null;\n newBaselines[url] = currentHash;\n\n if (!previousHash) {\n results.push({ url, changed: false, reason: 'new_baseline', currentHash, previousHash: null });\n } else if (previousHash === currentHash) {\n results.push({ url, changed: false, reason: 'unchanged', currentHash, previousHash });\n } else {\n results.push({ url, changed: true, reason: 'content_changed', currentHash, previousHash });\n }\n } catch (fetchErr: any) {\n results.push({ url, changed: false, reason: `error:${fetchErr?.message ?? String(fetchErr)}`, currentHash: '', previousHash: null });\n }\n }\n\n // Persist updated baselines so next run has fresh reference points.\n await withTenant(tenantId, (tx) =>\n (tx as any).project.update({\n where: { id: projectId },\n data: { crawlConfig: { ...crawlCfg, monitorBaselines: newBaselines } as any },\n })\n ).catch((e: any) => logger.warn({ err: e }, '[monitor] baseline persist failed'));\n\n const changedUrls = results.filter(r => r.changed);\n\n if (changedUrls.length > 0) {\n dispatchWebhook(tenantId, projectId, 'content.changed', {\n jobId,\n changedUrls: changedUrls.map(r => ({ url: r.url, previousHash: r.previousHash, currentHash: r.currentHash })),\n totalChecked: results.length,\n changedCount: changedUrls.length,\n }).catch(console.error);\n }\n\n await updateJobProgress(tenantId, jobId, 100, 'Monitor check complete');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE',\n progress: 100,\n finishedAt: new Date(),\n result: { results, changedCount: changedUrls.length, totalChecked: results.length } as any,\n },\n })\n );\n } catch (err: any) {\n await recordJobFailure(tenantId, jobId, err);\n } finally {\n runningControllers.delete(jobId);\n }\n}\n\n// \u2500\u2500\u2500 IMPORT_TESTS job processor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Async counterpart to the synchronous POST /artifacts/:id/import-tests route.\n// Enqueued by POST /modernization/bulk-import for each artifact in a batch.\n// Config: { tenantId, projectId, artifactId, suiteId?, autoLabel?, sessionId? }\n\nasync function processImportTestsJob(jobId: string, config: any) {\n const { tenantId, projectId, artifactId, autoLabel, sourcePlatform } = config;\n const jobStart = new Date();\n let suiteId: string = config.suiteId ?? 'new';\n\n await updateJobProgress(tenantId, jobId, 5, 'Loading artifact');\n\n const artifact = await withTenant(tenantId, (tx) =>\n (tx as any).projectArtifact.findUnique({ where: { id: artifactId } })\n );\n if (!artifact || (artifact as any).tenantId !== tenantId) {\n throw new Error(`Artifact ${artifactId} not found`);\n }\n\n const content: string = (artifact as any).content ?? '';\n const filename: string = (artifact as any).originalName ?? (artifact as any).name ?? '';\n const kind: string = (artifact as any).kind ?? '';\n\n // Detect format using same logic as the synchronous route\n function detectFormat(fn: string, k: string, c: string): string {\n if (fn.endsWith('.feature')) return 'GHERKIN';\n if (fn.match(/\\.(xlsx|xls|csv)$/i) || k === 'SPREADSHEET') return 'EXCEL';\n if (fn.endsWith('.txt') && c && c.split('\\n').some((l) => l.trim().startsWith('|'))) return 'EXCEL';\n return 'AI_EXTRACT';\n }\n const format = detectFormat(filename, kind, content);\n\n await updateJobProgress(tenantId, jobId, 15, `Parsing ${filename}`);\n\n const { parseTestCases, labelTestCasesBatch } = await import('@detiq/agents');\n let res = await parseTestCases(content, format, tenantId, (artifact as any).columnMapping ?? undefined, undefined, undefined, sourcePlatform);\n if (res.cases.length === 0 && format !== 'AI_EXTRACT') {\n await updateJobProgress(tenantId, jobId, 25, 'Retrying with AI extraction');\n try {\n res = await parseTestCases(content, 'AI_EXTRACT', tenantId, undefined, undefined, undefined, sourcePlatform);\n } catch {\n // AI unavailable (no LLM configured) \u2014 proceed with 0 cases\n }\n }\n\n const parsedCases = res.cases;\n if (parsedCases.length === 0) {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', progress: 100, finishedAt: new Date(), result: { imported: 0, skipped: 0, labeled: 0 } as any },\n })\n );\n return;\n }\n\n await updateJobProgress(tenantId, jobId, 35, `Parsed ${parsedCases.length} cases \u2014 creating suite`);\n\n // Auto-create suite from filename when suiteId === 'new'\n if (!suiteId || suiteId === 'new') {\n const suiteName = filename.replace(/\\.[^/.]+$/, '') || 'Imported Tests';\n const suite = await withTenant(tenantId, (tx) =>\n (tx as any).testSuite.create({\n data: { tenantId, projectId, name: suiteName },\n })\n );\n suiteId = (suite as any).id;\n }\n\n await updateJobProgress(tenantId, jobId, 50, `Importing ${parsedCases.length} cases`);\n\n const { created, skipped } = await AppBrain.importTestCasesFromArtifact(\n tenantId, projectId, suiteId, parsedCases, { duplicateMode: 'skip', artifactId }\n );\n\n let labeled = 0;\n if (autoLabel && created.length > 0) {\n await updateJobProgress(tenantId, jobId, 75, `AI labeling ${created.length} cases`);\n try {\n const labelInput = created.map((c: any) => ({\n id: c.id, title: c.title,\n description: c.description ?? '',\n stepsText: (Array.isArray(c.steps) ? c.steps.join(' ') : String(c.steps ?? '')).slice(0, 200),\n }));\n const labelResults = await labelTestCasesBatch(labelInput, tenantId);\n // Persist labels back to each test case\n await Promise.all(\n labelResults.map((lr: any) =>\n withTenant(tenantId, (tx) =>\n tx.testCase.update({\n where: { id: lr.id },\n data: {\n priority: lr.priority ?? undefined,\n kind: lr.kind ?? undefined,\n tags: lr.tags ?? undefined,\n status: lr.needsReview ? 'PENDING_REVIEW' : undefined,\n },\n })\n ).catch(() => { }) // non-fatal per-case\n )\n );\n labeled = labelResults.length;\n } catch {\n // non-fatal \u2014 import succeeded even if labeling fails\n }\n }\n\n // Auto-generate AI test data for imported cases that have recorded form fields (testDataInline)\n const casesNeedingData = created.filter((c: any) => {\n const inline = (c as any).testDataInline;\n return inline && typeof inline === 'object' && Object.keys(inline).length > 0;\n });\n if (casesNeedingData.length > 0) {\n const cap = Math.min(casesNeedingData.length, 20);\n await updateJobProgress(tenantId, jobId, 90, `Generating test data for ${cap} cases`);\n const { generateTestData, refreshTestDataSkillFile } = await import('@detiq/agents');\n for (let i = 0; i < cap; i++) {\n const c = casesNeedingData[i];\n await generateTestData({ tenantId, projectId, testCaseId: c.id, jobId }).catch((e: any) => {\n logger.warn({ err: e?.message, testCaseId: c.id }, '[import] test data generation failed');\n });\n }\n refreshTestDataSkillFile(tenantId, projectId).catch(console.error);\n }\n\n if (created.length > 0) {\n refreshTestCasesSkillFile(tenantId, projectId).catch(console.error);\n }\n\n await updateJobProgress(tenantId, jobId, 98, `Done \u2014 ${created.length} imported`);\n\n // Write final result and mark job DONE\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE',\n progress: 100,\n finishedAt: new Date(),\n result: {\n imported: created.length,\n skipped: skipped ?? 0,\n labeled,\n suiteId,\n artifactId,\n artifactName: filename,\n } as any,\n },\n })\n );\n aggregateJobTokens(tenantId, jobId, projectId, jobStart).catch(console.error);\n}\n\n// Generate test cases for a single screen, enqueued by NEW_PAGES_PIPELINE.\n// Uses the same DualContextOrchestrator path as the in-process GENERATE_TESTS route.\nasync function processGenerateScreenTestsJob(jobId: string, config: any) {\n const { tenantId, projectId, screenId, repoConnectionId, triggerAlignedScripts } = config;\n const jobStartedAt = new Date();\n try {\n await withTenant(tenantId, (tx: any) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: jobStartedAt } })\n );\n\n const { DualContextOrchestrator } = await import('@detiq/agents');\n await updateJobProgress(tenantId, jobId, 10, 'Generating test cases for screen');\n\n const crawlCfg = await AppBrain.getCrawlConfig(tenantId, projectId).catch(() => null);\n const deviceProfiles: string[] = (crawlCfg as any)?.deviceProfiles ?? ['DESKTOP'];\n\n const existingAiCases: number = await withTenant(tenantId, (tx) =>\n (tx as any).testCase.count({ where: { tenantId, screenId, source: 'AI_GENERATED' } })\n );\n const generationMode = existingAiCases > 0 ? 'replace' : undefined;\n\n for (const viewport of deviceProfiles) {\n await DualContextOrchestrator.execute({ tenantId, projectId, targetScreenId: screenId, jobId, viewport, ...(generationMode ? { mode: generationMode } : {}) });\n }\n\n await updateJobProgress(tenantId, jobId, 80, 'Test cases generated');\n\n // Optionally kick framework-aligned script generation for this screen's test cases\n if (triggerAlignedScripts && repoConnectionId) {\n const conn: any = await withTenant(tenantId, (tx: any) =>\n tx.gitHubRepoConnection.findFirst({ where: { id: repoConnectionId, tenantId } })\n );\n if (conn?.styleProfile) {\n const { generateScriptAligned, mapWithConcurrency, SCRIPT_GEN_CONCURRENCY } = await import('@detiq/agents');\n const cases = await withTenant(tenantId, (tx: any) =>\n tx.testCase.findMany({ where: { tenantId, projectId, screenId, status: 'APPROVED' }, select: { id: true }, take: 50 })\n );\n await mapWithConcurrency(cases as any[], SCRIPT_GEN_CONCURRENCY, (tc: any) =>\n generateScriptAligned({\n tenantId,\n projectId,\n testCaseId: tc.id,\n styleProfile: conn.styleProfile,\n pageObjectInventory: conn.pageObjectInventory ?? undefined,\n }).catch(() => { })\n );\n }\n }\n\n await withTenant(tenantId, (tx: any) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', progress: 100, finishedAt: new Date() } })\n );\n aggregateJobTokens(tenantId, jobId, projectId, jobStartedAt).catch(console.error);\n } catch (err: any) {\n const msg = err?.message ?? String(err);\n logger.error({ jobId, screenId, err: msg }, 'processGenerateScreenTestsJob failed');\n await recordJobFailure(tenantId, jobId, err);\n }\n}\n\ntype CaptureStepStatus = 'PASSED' | 'FAILED' | 'HEALED' | 'SKIPPED';\ntype CaptureStepResult = {\n stepNumber: number;\n description: string;\n status: CaptureStepStatus;\n screenshotPath?: string;\n screenshotUrl?: string;\n screenId?: string;\n url?: string;\n elementsCount?: number;\n healed?: boolean;\n healTier?: 'mechanical' | 'ai';\n healReasoning?: string;\n error?: string;\n};\n\nfunction captureStepText(step: unknown): string {\n if (typeof step === 'string') return step;\n if (!step || typeof step !== 'object') return '';\n const obj = step as Record<string, unknown>;\n return [obj.action, obj.text, obj.label, obj.data, obj.expected]\n .filter((v): v is string => typeof v === 'string' && v.trim().length > 0)\n .join(' -> ');\n}\n\nfunction firstQuoted(text: string): string | undefined {\n return (text.match(/\"([^\"]+)\"/)?.[1] ?? text.match(/'([^']+)'/)?.[1])?.trim();\n}\n\nfunction fallbackExecutionPlan(rawSteps: unknown[]): ExecutionStep[] {\n const plan: ExecutionStep[] = [];\n for (const raw of rawSteps) {\n const text = captureStepText(raw).trim();\n if (!text) continue;\n const stepNumber = plan.length + 1;\n const url = text.match(/https?:\\/\\/[^\\s\"']+/)?.[0];\n\n if (/^navigate\\b|^go to\\b|^open\\b/i.test(text) && url) {\n plan.push({ stepNumber, description: text, action: 'navigate', url });\n continue;\n }\n\n const urlContains = text.match(/verify url contains:\\s*[\"']?([^\"']+)[\"']?/i);\n if (urlContains) {\n plan.push({ stepNumber, description: text, action: 'assert_url', value: urlContains[1].trim() });\n continue;\n }\n\n const inputMatch = text.match(/(?:enter|type|fill)\\s+[\"']([^\"']+)[\"']\\s+(?:in|into)\\s+(.+?)(?:\\s+field|\\s+input|\\s+box|\\s+area)?(?:\\s*\u2192.*)?$/i);\n if (inputMatch) {\n const selector = inputMatch[2].replace(/^(the\\s+)?/i, '').replace(/\\s+(field|input|box|area)$/i, '').trim();\n plan.push({ stepNumber, description: text, action: 'fill', selectorStrategy: 'label', selector, value: inputMatch[1].trim() });\n continue;\n }\n\n const quoted = firstQuoted(text);\n if (/^(click|tap|navigate menu|check|select)\\b/i.test(text) && quoted) {\n plan.push({ stepNumber, description: text, action: 'click', selectorStrategy: 'text', selector: quoted });\n continue;\n }\n if (/^(verify|assert|expect)\\b/i.test(text) && quoted) {\n plan.push({ stepNumber, description: text, action: 'assert_visible', selectorStrategy: 'text', selector: quoted });\n continue;\n }\n if (/execute script/i.test(text)) {\n const script = text.split(':').slice(1).join(':').replace(/\\s*\u2192.*$/, '').trim();\n if (script) plan.push({ stepNumber, description: text, action: 'script', value: script });\n }\n }\n return plan;\n}\n\nfunction screenNameFromUrl(url: string, fallback: string): string {\n try {\n const u = new URL(url);\n return u.pathname.replace(/\\/+$/, '').split('/').filter(Boolean).pop() || u.hostname || fallback;\n } catch {\n return fallback;\n }\n}\n\nasync function processCaptureStepsJob(jobId: string, config: any) {\n const { tenantId, projectId, testCaseId } = config as { tenantId: string; projectId: string; testCaseId: string };\n const screenshotDir = config.screenshotDir || platformConfig.screenshotDir || process.env.SCREENSHOT_DIR || '/tmp/zeta-screenshots';\n await fs.mkdir(screenshotDir, { recursive: true });\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'RUNNING', startedAt: new Date(), progress: 2 } })\n );\n\n let browser: any;\n try {\n await updateJobProgress(tenantId, jobId, 5, 'Loading test case');\n const [tc, crawlCfg, tenantStorageCfg] = await Promise.all([\n AppBrain.getTestCaseForExecution(tenantId, testCaseId) as Promise<any>,\n AppBrain.getCrawlConfig(tenantId, projectId).catch(() => null),\n getTenantStorageConfig(tenantId).catch(() => null),\n ]);\n if (!tc) throw new Error('Test case not found');\n\n const rawSteps = Array.isArray(tc.steps) ? tc.steps : [];\n if (rawSteps.length === 0) throw new Error('Test case has no steps to capture');\n\n // Resolve env tokens from testDataInline so {{fze.env.URL}} \u2192 concrete URL before execution plan\n const _tdi = ((tc as any).testDataInline ?? {}) as Record<string, unknown>;\n const _tdiStartUrl = typeof _tdi['startUrl'] === 'string' && (_tdi['startUrl'] as string).startsWith('http')\n ? (_tdi['startUrl'] as string) : '';\n const _recordedUrls = Array.isArray(_tdi['recordedUrls']) ? (_tdi['recordedUrls'] as unknown[]).map(String) : [];\n const _recordedStartUrl = _recordedUrls.find((u) => u.startsWith('http')) ?? '';\n\n const baseUrl = config.baseUrl || _tdiStartUrl || _recordedStartUrl || tc?.screen?.project?.appUrl || crawlCfg?.appUrl || '';\n\n // Build env token \u2192 concrete value map from testDataInline for step text substitution\n const _envTokenMap: Record<string, string> = {};\n for (const [k, v] of Object.entries(_tdi)) {\n if (typeof v === 'string' && v.startsWith('http')) _envTokenMap[k] = v;\n }\n if (_recordedStartUrl && !_envTokenMap['startUrl']) _envTokenMap['startUrl'] = _recordedStartUrl;\n\n // Replace {{fze.env.X}} and [env:X] tokens in step texts with concrete values from testDataInline.\n // IMPORTANT: only substitute if we have a concrete value for that key.\n // Fall back to [key] placeholder \u2014 never fall back to baseUrl (would inject URL into credential fields).\n const resolveStepText = (text: string): string =>\n text\n .replace(/\\{\\{\\s*fze\\.env\\.([A-Za-z_][A-Za-z0-9_]*)\\s*\\}\\}/g, (_m, k) => _envTokenMap[k] ?? `[${k}]`)\n .replace(/\\[env:([A-Za-z_][A-Za-z0-9_]*)\\]/g, (_m, k) => _envTokenMap[k] ?? `[${k}]`);\n\n const stepTexts = rawSteps.map(captureStepText).filter(Boolean).map(resolveStepText);\n if (stepTexts.length === 0) throw new Error('Test case has no steps to capture');\n\n let plan = await generateExecutionPlan(tenantId, { ...tc, steps: stepTexts }, baseUrl).catch(() => []);\n plan = Array.isArray(plan)\n ? plan.filter(Boolean).map((step: any, idx: number) => ({ ...step, stepNumber: idx + 1 }))\n : [];\n if (plan.length === 0) plan = fallbackExecutionPlan(rawSteps);\n if (plan.length === 0) throw new Error('Could not generate execution plan from steps');\n\n const { chromium } = await import('playwright');\n browser = await chromium.launch({\n headless: true,\n ...(process.env.PLAYWRIGHT_EXECUTABLE_PATH ? { executablePath: process.env.PLAYWRIGHT_EXECUTABLE_PATH } : {}),\n });\n let parsedStorageState: any;\n try {\n parsedStorageState = config.storageState ? JSON.parse(config.storageState) : crawlCfg?.storageState ? JSON.parse(crawlCfg.storageState) : undefined;\n } catch {\n parsedStorageState = undefined;\n }\n const context = await browser.newContext({ viewport: { width: 1280, height: 800 }, ...(parsedStorageState ? { storageState: parsedStorageState } : {}) });\n const page = await context.newPage();\n const aiHealingEnabled = await isFeatureEnabled(tenantId, 'AI_SELF_HEALING').catch(() => false);\n\n const stepResults: CaptureStepResult[] = [];\n const screens: Array<{ stepNumber: number; screenId: string; url: string; screenshotPath?: string; screenshotUrl?: string; isNewScreen: boolean }> = [];\n const locators: any[] = [];\n const seenLocatorExpressions = new Set<string>();\n const seenScreenKeys = new Set<string>();\n let lastStateKey = '';\n\n for (let i = 0; i < plan.length; i++) {\n const step = plan[i];\n const description = step.description || stepTexts[i] || `Step ${i + 1}`;\n const stepNumber = i + 1;\n const result: CaptureStepResult = { stepNumber, description, status: 'FAILED' };\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n progress: Math.min(90, Math.round((i / Math.max(plan.length, 1)) * 90)),\n result: { currentStep: { stepNumber, description } } as any,\n heartbeatAt: new Date(),\n },\n })\n ).catch(() => { });\n\n try {\n const heal = await executeExecutionStep(page, step, aiHealingEnabled ? { healCtx: { tenantId, projectId } } : {});\n if (heal.healed) {\n result.healed = true;\n result.healTier = heal.healedVia;\n result.healReasoning = heal.healReasoning;\n }\n await page.waitForLoadState('networkidle', { timeout: parseInt(process.env.TEST_NETWORK_IDLE_TIMEOUT_MS ?? '2000', 10) }).catch(() => { });\n result.status = result.healed ? 'HEALED' : 'PASSED';\n } catch (err: any) {\n result.status = step.optional ? 'SKIPPED' : 'FAILED';\n result.error = String(err?.message ?? err).slice(0, 300);\n }\n\n const currentUrl = page.url();\n result.url = currentUrl;\n const ssFilename = `${testCaseId}-capture-step-${stepNumber}${result.status === 'FAILED' ? '-err' : ''}.png`;\n const ssPath = path.join(screenshotDir, ssFilename);\n await page.screenshot({ path: ssPath, fullPage: false }).catch(() => { });\n result.screenshotPath = ssPath;\n\n let cloudUrl: string | undefined;\n if (tenantStorageCfg) {\n const key = buildArtifactKey(tenantId, projectId, 'screenshots', ssFilename, tenantStorageCfg.prefix);\n cloudUrl = (await uploadScreenshotToTenantStorage(tenantStorageCfg, ssPath, key).catch(() => undefined)) ?? undefined;\n if (cloudUrl) result.screenshotUrl = cloudUrl;\n }\n\n const elements = await domFallbackElements(page).catch(() => []);\n result.elementsCount = elements.length;\n const stateHash = hashContent(elements.map((el) => `${el.role}:${el.meaning}:${el.notes ?? ''}`).join('|'));\n const screenKey = `${currentUrl || 'about:blank'}:${stateHash}`;\n const shouldPersistScreen = currentUrl && currentUrl !== 'about:blank' && (i === 0 || screenKey !== lastStateKey || !seenScreenKeys.has(screenKey));\n lastStateKey = screenKey;\n\n if (shouldPersistScreen) {\n const screenName = screenNameFromUrl(currentUrl, tc.title ?? `Step ${stepNumber}`);\n const screen = await AppBrain.saveScreen(tenantId, projectId, screenName, currentUrl, jobId, i === 0 ? 'ENTRY_POINT' : 'LINK_FOLLOW').catch(() => null) as any;\n if (screen?.id) {\n result.screenId = screen.id;\n await AppBrain.saveScreenshot(tenantId, projectId, screen.id, ssPath, screen.version ?? 1, 'DESKTOP', cloudUrl).catch(() => { });\n for (const el of elements) {\n await AppBrain.saveElement(\n tenantId, projectId, screen.id,\n el.meaning, el.role, el.expectedData, el.notes,\n undefined, true, el.boundingRect, el.ariaState, el.parentLandmark,\n ).catch(() => { });\n }\n for (const loc of elementsToPlaywrightLocators(elements as any)) {\n if (seenLocatorExpressions.has(loc.expression)) continue;\n seenLocatorExpressions.add(loc.expression);\n locators.push(loc);\n }\n if (!seenScreenKeys.has(screenKey)) {\n screens.push({ stepNumber, screenId: screen.id, url: currentUrl, screenshotPath: ssPath, screenshotUrl: cloudUrl, isNewScreen: true });\n seenScreenKeys.add(screenKey);\n }\n }\n }\n\n stepResults.push(result);\n if (result.status === 'FAILED') break;\n }\n\n if (locators.length > 0) {\n await AppBrain.saveLocatorsJson(tenantId, testCaseId, locators as any).catch(() => { });\n }\n\n const firstScreen = screens[0];\n await withTenant(tenantId, (tx) =>\n (tx as any).testCase.update({\n where: { id: testCaseId },\n data: {\n ...(firstScreen?.screenId ? { screenId: firstScreen.screenId } : {}),\n captureStepScreenshots: stepResults as any,\n },\n })\n ).catch(() => { });\n\n const finalResult = {\n stepResults,\n screens,\n locators,\n totalSteps: plan.length,\n capturedScreens: screens.length,\n };\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', progress: 100, result: finalResult as any, finishedAt: new Date(), heartbeatAt: new Date() },\n })\n );\n } catch (err: any) {\n const error = String(err?.message ?? err);\n logger.error({ jobId, testCaseId, err: error }, 'processCaptureStepsJob failed');\n await recordJobFailure(tenantId, jobId, err);\n } finally {\n await browser?.close?.().catch(() => { });\n }\n}\n\n// \u2500\u2500 Multi-role crawl orchestrator \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Runs a primary CRAWL to discover all screens, then fans out one ROLE_CRAWL\n// job per credential profile. Role jobs run concurrently via the worker pool.\n\nasync function processMultiRoleCrawlJob(jobId: string, config: any) {\n const { tenantId, projectId } = config;\n try {\n await withTenant(tenantId, (tx) =>\n tx.platformJob.updateMany({\n where: { id: jobId, status: { not: 'CANCELLED' } },\n data: { status: 'RUNNING', startedAt: new Date(), heartbeatAt: new Date(), workerId: WORKER_ID },\n })\n );\n\n await updateJobProgress(tenantId, jobId, 5, 'Loading project crawl config\u2026');\n\n // Load saved project crawlConfig for primary crawl parameters\n const proj = await withTenant(tenantId, (tx) =>\n (tx as any).project.findUnique({ where: { id: projectId }, select: { crawlConfig: true } })\n ) as { crawlConfig: any } | null;\n const savedCrawlConfig = proj?.crawlConfig ?? {};\n\n const credentialProfiles: Array<{ id: string; name: string; username: string; password: string }> =\n config.credentialProfiles ?? [];\n\n // \u2500\u2500 Step 1: enqueue primary CRAWL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n await updateJobProgress(tenantId, jobId, 10, 'Enqueuing primary crawl\u2026');\n const primaryJobId = await enqueueJob('CRAWL', {\n tenantId,\n projectId,\n appUrl: config.appUrl || savedCrawlConfig.appUrl,\n credentials: savedCrawlConfig.username && savedCrawlConfig.password\n ? { username: savedCrawlConfig.username, password: savedCrawlConfig.password, ...(savedCrawlConfig.totpSecret ? { totpSecret: savedCrawlConfig.totpSecret } : {}) }\n : undefined,\n storageState: savedCrawlConfig.storageState,\n screenshotDir: platformConfig.screenshotDir,\n startUrls: savedCrawlConfig.seedUrls?.length ? savedCrawlConfig.seedUrls : undefined,\n deviceProfiles: savedCrawlConfig.deviceProfiles ?? ['DESKTOP'],\n loginInstructions: savedCrawlConfig.loginInstructions,\n proxyUrl: savedCrawlConfig.proxyUrl,\n useSitemap: true,\n sitemapUrl: savedCrawlConfig.sitemapUrl,\n });\n logger.info({ primaryJobId, projectId }, '[multi-role-crawl] primary CRAWL enqueued');\n\n // \u2500\u2500 Step 2: poll until primary CRAWL is done (2-hour timeout) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const PRIMARY_TIMEOUT_MS = 2 * 60 * 60 * 1000;\n const POLL_INTERVAL_MS = 10_000;\n const deadline = Date.now() + PRIMARY_TIMEOUT_MS;\n let primaryStatus = 'QUEUED';\n while (Date.now() < deadline) {\n const primaryJob = await prismaAdmin.platformJob.findUnique({\n where: { id: primaryJobId },\n select: { status: true },\n }).catch(() => null);\n primaryStatus = primaryJob?.status ?? 'UNKNOWN';\n if (primaryStatus === 'DONE' || primaryStatus === 'FAILED' || primaryStatus === 'CANCELLED') break;\n await updateJobProgress(tenantId, jobId, 20, `Waiting for primary crawl (${primaryStatus})\u2026`);\n await new Promise((res) => setTimeout(res, POLL_INTERVAL_MS));\n }\n\n if (primaryStatus !== 'DONE') {\n throw new Error(`Primary crawl ${primaryJobId} ended with status ${primaryStatus} \u2014 aborting role crawls`);\n }\n\n // \u2500\u2500 Step 3: collect all known screen URLs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n await updateJobProgress(tenantId, jobId, 50, 'Collecting screen URLs for role crawls\u2026');\n const screens = await prismaAdmin.screen.findMany({\n where: { tenantId, projectId, status: 'ACTIVE' },\n select: { id: true, url: true },\n }).catch(() => [] as Array<{ id: string; url: string | null }>);\n const screenUrls = screens.filter((s) => s.url).map((s) => s.url as string);\n logger.info({ screenCount: screenUrls.length, projectId }, '[multi-role-crawl] screen URLs collected');\n\n // \u2500\u2500 Step 4: enqueue one ROLE_CRAWL per credential profile \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (credentialProfiles.length === 0) {\n logger.warn({ projectId, jobId }, '[multi-role-crawl] no credential profiles \u2014 nothing to role-crawl');\n }\n\n const roleJobs: Array<{ roleName: string; jobId: string }> = [];\n for (const profile of credentialProfiles) {\n if (!profile.username || !profile.password) continue;\n const roleJobId = await enqueueJob('ROLE_CRAWL', {\n tenantId,\n projectId,\n appUrl: config.appUrl || savedCrawlConfig.appUrl,\n credentials: { username: profile.username, password: profile.password },\n roleProfileName: profile.name,\n parentJobId: jobId,\n selectiveUrls: config.fullReCrawl ? undefined : (screenUrls.length > 0 ? screenUrls : undefined),\n startUrls: config.fullReCrawl ? (savedCrawlConfig.seedUrls ?? []) : undefined,\n maxScreens: config.fullReCrawl ? undefined : screenUrls.length + 10,\n screenshotDir: platformConfig.screenshotDir,\n storageState: savedCrawlConfig.storageState,\n deviceProfiles: savedCrawlConfig.deviceProfiles ?? ['DESKTOP'],\n loginInstructions: savedCrawlConfig.loginInstructions,\n proxyUrl: savedCrawlConfig.proxyUrl,\n });\n roleJobs.push({ roleName: profile.name, jobId: roleJobId });\n logger.info({ roleJobId, roleName: profile.name, projectId }, '[multi-role-crawl] ROLE_CRAWL enqueued');\n }\n\n // \u2500\u2500 Step 5: poll until ALL role crawls complete \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const ROLE_TIMEOUT_MS = 2 * 60 * 60 * 1000;\n const roleDeadline = Date.now() + ROLE_TIMEOUT_MS;\n const roleResults: Array<{ roleName: string; jobId: string; status: string; screensTagged?: number }> = [];\n\n while (Date.now() < roleDeadline && roleJobs.length > 0) {\n const pending: typeof roleJobs = [];\n for (const rj of roleJobs) {\n const rJob = await prismaAdmin.platformJob.findUnique({\n where: { id: rj.jobId },\n select: { status: true, result: true },\n }).catch(() => null);\n const st = rJob?.status ?? 'UNKNOWN';\n if (st === 'DONE' || st === 'FAILED' || st === 'CANCELLED') {\n roleResults.push({\n roleName: rj.roleName,\n jobId: rj.jobId,\n status: st,\n screensTagged: (rJob?.result as any)?.screensTagged,\n });\n } else {\n pending.push(rj);\n }\n }\n roleJobs.length = 0;\n roleJobs.push(...pending);\n if (roleJobs.length === 0) break;\n const pct = Math.round(60 + ((roleResults.length / (roleResults.length + roleJobs.length)) * 35));\n await updateJobProgress(tenantId, jobId, pct, `Role crawls: ${roleResults.length} done, ${roleJobs.length} pending\u2026`);\n await new Promise((res) => setTimeout(res, POLL_INTERVAL_MS));\n }\n\n // Any still-pending jobs timed out \u2014 record them as timed out\n for (const rj of roleJobs) {\n roleResults.push({ roleName: rj.roleName, jobId: rj.jobId, status: 'TIMEOUT' });\n }\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE',\n progress: 100,\n finishedAt: new Date(),\n result: { primaryJobId, roleJobs: roleResults } as any,\n },\n })\n );\n logger.info({ jobId, primaryJobId, roleCount: roleResults.length, projectId }, '[multi-role-crawl] completed');\n } catch (err: any) {\n const msg = String(err?.message ?? err);\n logger.error({ jobId, err: msg }, '[multi-role-crawl] failed');\n await recordJobFailure(tenantId, jobId, err);\n }\n}\n\n// \u2500\u2500 Role-specific sub-crawl \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Crawls the app as a specific credential profile and tags all discovered\n// screens with the role name in the roleAccess array column.\n\nasync function processRoleCrawlJob(jobId: string, config: any) {\n const { tenantId, projectId } = config;\n try {\n const claimed = await withTenant(tenantId, (tx) =>\n tx.platformJob.updateMany({\n where: { id: jobId, status: { not: 'CANCELLED' } },\n data: { status: 'RUNNING', startedAt: new Date(), heartbeatAt: new Date(), workerId: WORKER_ID },\n })\n );\n if (claimed.count === 0) return;\n\n const controller = new AbortController();\n runningControllers.set(jobId, controller);\n const heartbeatTimer = setInterval(() => void heartbeatJob(tenantId, jobId), JOB_HEARTBEAT_INTERVAL_MS);\n\n const onProgress = async (pct: number, step: string) => {\n await updateJobProgress(tenantId, jobId, pct, step);\n };\n\n try {\n // Load project crawlConfig for proxy, storageState overrides\n const proj = await withTenant(tenantId, (tx) =>\n (tx as any).project.findUnique({ where: { id: projectId }, select: { crawlConfig: true } })\n ) as { crawlConfig: any } | null;\n const savedCrawlConfig = proj?.crawlConfig ?? {};\n\n // Build runtime credentials with TOTP support\n const runtimeCreds = config.credentials\n ? {\n ...config.credentials,\n ...(config.credentials.totpSecret\n ? { getOneTimeCode: async () => generateTOTP(config.credentials.totpSecret) }\n : {}),\n }\n : undefined;\n\n const storageConfigForCrawl = await getTenantStorageConfig(tenantId).catch(() => null);\n\n const result = await crawlProject({\n ...config,\n credentials: runtimeCreds,\n storageState: config.storageState ?? savedCrawlConfig.storageState,\n proxyUrl: config.proxyUrl ?? savedCrawlConfig.proxyUrl,\n loginInstructions: config.loginInstructions ?? savedCrawlConfig.loginInstructions,\n abortSignal: controller.signal,\n jobId,\n storageConfig: storageConfigForCrawl,\n }, onProgress);\n\n clearInterval(heartbeatTimer);\n runningControllers.delete(jobId);\n\n // Tag discovered screens with this role\n const roleName = config.roleProfileName as string;\n const discovered: any[] = (result as any).discovered ?? [];\n const roleScreenIds = discovered.filter((d: any) => d.screenId).map((d: any) => d.screenId as string);\n\n if (roleScreenIds.length > 0 && roleName) {\n await prismaAdmin.$executeRaw`\n UPDATE \"Screen\"\n SET \"roleAccess\" = array(SELECT DISTINCT unnest(array_append(\"roleAccess\", ${roleName}::text)))\n WHERE id = ANY(${roleScreenIds}::text[])\n AND \"tenantId\" = ${tenantId}\n `.catch((e: any) => logger.warn({ err: e?.message, roleName, count: roleScreenIds.length }, '[role-crawl] roleAccess tag failed (non-fatal)'));\n }\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: 'DONE',\n progress: 100,\n finishedAt: new Date(),\n result: { screensTagged: roleScreenIds.length, roleName } as any,\n },\n })\n );\n logger.info({ jobId, roleName, screensTagged: roleScreenIds.length, projectId }, '[role-crawl] completed');\n } catch (err: any) {\n clearInterval(heartbeatTimer);\n runningControllers.delete(jobId);\n const aborted = String(err).includes('CRAWL_ABORTED');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: {\n status: aborted ? 'CANCELLED' : 'FAILED',\n error: aborted ? 'Stopped by user' : String(err),\n finishedAt: new Date(),\n },\n })\n );\n } finally {\n runningControllers.delete(jobId);\n }\n } catch (err: any) {\n const msg = String(err?.message ?? err);\n logger.error({ jobId, err: msg }, '[role-crawl] outer failure');\n await recordJobFailure(tenantId, jobId, err);\n }\n}\n\nasync function processGenerateScriptJob(jobId: string, config: any): Promise<void> {\n const { tenantId, projectId, language } = config;\n try {\n await updateJobProgress(tenantId, jobId, 1, 'Loading test cases\u2026').catch(() => {});\n\n if (config.packId) {\n // Pack script (regression / smoke / sanity / etc.)\n const { generatePackScript } = await import('@detiq/agents');\n const result = await generatePackScript({ tenantId, projectId, packType: config.packId, language });\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', progress: 100, finishedAt: new Date(), result: result as any },\n })\n );\n } else {\n // Suite script\n const { generateSuiteScript } = await import('@detiq/agents');\n let progressChain: Promise<void> = Promise.resolve();\n const result = await generateSuiteScript({\n tenantId, projectId, suiteId: config.suiteId, language,\n caseIds: config.caseIds ?? undefined,\n useV2: config.useV2,\n styleProfile: config.repoStyleProfile ?? undefined,\n pageObjectInventory: config.repoPageObjects ?? undefined,\n jobId,\n onProgress: (done: number, total: number) => {\n progressChain = progressChain\n .then(() => updateJobProgress(tenantId, jobId, Math.floor((done / total) * 95), `Generated ${done}/${total} scripts`))\n .catch(() => {});\n },\n });\n await progressChain.catch(() => {});\n await import('@detiq/agents')\n .then(({ refreshScriptInventory }) => refreshScriptInventory(tenantId, projectId))\n .catch((e: any) => logger.warn({ err: e?.message }, '[generate-script] refreshScriptInventory failed'));\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', progress: 100, finishedAt: new Date(), result: result as any },\n })\n );\n }\n } catch (e: any) {\n await finishTrackedJob(tenantId, jobId, e?.message ?? 'generate-script failed');\n }\n}\n\nfunction queueImportedArtifactSkillRefresh(tenantId: string, projectId: string, reason: string): void {\n setImmediate(() => {\n import('@detiq/agents')\n .then((agentsModule) => {\n const agents = agentsModule as any;\n return Promise.allSettled([\n agents.refreshArtifactKnowledgeSkillFile(tenantId, projectId),\n agents.refreshRequirementsCoverageSkillFile(tenantId, projectId),\n ]);\n })\n .then((results) => {\n results?.forEach((result) => {\n if (result.status === 'rejected') {\n console.warn(`[artifacts] skill refresh failed after ${reason}:`, result.reason?.message ?? result.reason);\n }\n });\n })\n .catch((e) => console.warn(`[artifacts] skill refresh failed after ${reason}:`, e?.message ?? e));\n });\n}\n\n// Registers the type\u2192processor dispatch table used by the durable worker (both the\n// enqueueJob() fast-path kick and startJobWorkerLoop()'s poll). Declared at module\n// bottom so it can reference every process*Job function by name; function\n// declarations are hoisted, so this could technically live anywhere in the file,\n// but keeping it here alongside the last definition keeps the mapping easy to audit\n// against the actual functions above.\nconst CONTENT_DETECTED_DOC_TYPES = new Set(['FUNCTIONIZE_TC', 'SELENIUM_SCRIPT', 'ENV_CONFIG']);\nconst AI_DOC_TYPES = new Set([\n 'REQUIREMENTS', 'USER_STORY', 'TEST_CASES', 'TEST_DATA', 'AUTOMATION_SCRIPT', 'API_SPEC', 'ARCHITECTURE', 'REPORT', 'OTHER',\n 'SPREADSHEET', 'DATASHEET',\n]);\nconst AI_DOC_TYPE_ALIASES: Record<string, string> = {\n REQUIREMENT: 'REQUIREMENTS', SPEC: 'REQUIREMENTS', SPECIFICATION: 'REQUIREMENTS',\n USER_STORIES: 'USER_STORY', STORY: 'USER_STORY',\n TEST_CASE: 'TEST_CASES', TESTCASES: 'TEST_CASES',\n TESTDATA: 'TEST_DATA', SCRIPT: 'AUTOMATION_SCRIPT', AUTOMATION: 'AUTOMATION_SCRIPT',\n};\nfunction normalizeAiDocType(raw: unknown): string {\n const key = String(raw ?? '').trim().toUpperCase().replace(/[\\s-]+/g, '_');\n const mapped = AI_DOC_TYPE_ALIASES[key] ?? key;\n return AI_DOC_TYPES.has(mapped) ? mapped : 'OTHER';\n}\n\nasync function processArtifactIntelJob(jobId: string, config: any) {\n const { tenantId, projectId, artifactId, artifactName, isReprocess } = config;\n try {\n await updateJobProgress(tenantId, jobId, 10, 'Downloading artifact content');\n \n const artifact = await withTenant(tenantId, (tx) =>\n (tx as any).projectArtifact.findUnique({ where: { id: artifactId }, select: { storageKey: true, docType: true } })\n ) as any;\n \n if (!artifact || !artifact.storageKey) {\n await updateJobProgress(tenantId, jobId, 100, 'Artifact deleted or missing content, skipping.');\n await withTenant(tenantId, (tx) =>\n (tx as any).platformJob.update({ where: { id: jobId }, data: { status: 'DONE', finishedAt: new Date() } })\n );\n return;\n }\n\n const storageCfg = await getTenantStorageConfig(tenantId);\n if (!storageCfg) {\n throw new Error('Tenant storage configuration missing.');\n }\n const contentBuffer = await downloadBufferFromTenantStorage(storageCfg, artifact.storageKey);\n const content = contentBuffer.toString('utf8');\n\n await updateJobProgress(tenantId, jobId, 40, 'Generating intelligence');\n\n const intel = await generateArtifactIntelligence(tenantId, projectId, artifactId, content, artifactName, jobId);\n \n const finalDocType = artifact.docType && CONTENT_DETECTED_DOC_TYPES.has(artifact.docType)\n ? artifact.docType\n : normalizeAiDocType(intel.docType);\n\n if (finalDocType !== intel.docType) {\n await withTenant(tenantId, (tx) =>\n (tx as any).projectArtifact.update({ where: { id: artifactId }, data: { docType: finalDocType } })\n ).catch((err: any) => console.warn(`[artifact-intel] docType fix-up failed for \"${artifactName}\":`, err?.message));\n intel.docType = finalDocType;\n }\n\n await updateJobProgress(tenantId, jobId, 90, 'Saving results');\n \n await withTenant(tenantId, (tx) =>\n (tx as any).platformJob.update({ where: { id: jobId }, data: { result: { artifactId, artifactName, summary: intel.summary, keywords: intel.keywords, docType: intel.docType }, status: 'DONE', finishedAt: new Date(), progress: 100 } })\n );\n\n queueImportedArtifactSkillRefresh(tenantId, projectId, isReprocess ? 'artifact-reprocess' : 'artifact-intel');\n } catch (err: any) {\n const msg = String(err?.message ?? err);\n logger.error({ jobId, err: msg }, '[artifact-intel] failed');\n await withTenant(tenantId, (tx) =>\n (tx as any).projectArtifact.update({ where: { id: artifactId }, data: { status: 'ERROR', summary: 'AI analysis failed' } })\n ).catch(() => { });\n await recordJobFailure(tenantId, jobId, err);\n queueImportedArtifactSkillRefresh(tenantId, projectId, 'artifact-intel-failed');\n }\n}\n\njobDispatchTable = {\n ARTIFACT_INTEL: processArtifactIntelJob,\n CRAWL: processJob,\n CAPTURE_STEPS: processCaptureStepsJob,\n DISCOVER: processDiscoverJob,\n MONITOR: processMonitorJob,\n NATIVE_CRAWL: processNativeCrawlJob,\n API_TESTS: processApiTestsJob,\n SECURITY_TESTS: processSecurityTestsJob,\n INTEGRATION_TESTS: processIntegrationTestsJob,\n FIGMA_IMPORT: processFigmaImportJob,\n GENERATE_SKILL_FILES: processGenerateSkillFilesJob,\n GENERATE_TESTS_FROM_REQS: processGenerateTestsFromReqsJob,\n GENERATE_SCREEN_TESTS: processGenerateScreenTestsJob,\n RUN_TEST_SUITE: processRunTestSuiteJob,\n REPO_SCAN: processRepoScanJob,\n IMPORT_TESTS: processImportTestsJob,\n BATCH_GENERATE_ALIGNED: processBatchGenerateAlignedJob,\n CREATE_PR: processCreatePRJob,\n NEW_PAGES_PIPELINE: processNewPagesPipelineJob,\n MULTI_ROLE_CRAWL: processMultiRoleCrawlJob,\n ROLE_CRAWL: processRoleCrawlJob,\n GENERATE_SCRIPT: processGenerateScriptJob,\n // The following job types are still processed in-process by the API.\n // If the worker claims one, the API crashed after creating it \u2014 mark failed so it's visible.\n GENERATE_TESTS: async (jobId: string, _config: any) => {\n logger.error({ jobId }, '[job-worker] GENERATE_TESTS claimed by worker \u2014 API did not process it in-process');\n await prismaAdmin.platformJob.update({\n where: { id: jobId },\n data: { status: 'FAILED', error: 'Test generation was not completed by the API \u2014 please retry from the UI.', finishedAt: new Date() },\n }).catch(() => { });\n },\n GENERATE_DATA: async (jobId: string, _config: any) => {\n logger.error({ jobId }, '[job-worker] GENERATE_DATA claimed by worker \u2014 API did not process it in-process');\n await prismaAdmin.platformJob.update({\n where: { id: jobId },\n data: { status: 'FAILED', error: 'Data generation was not completed by the API \u2014 please retry from the UI.', finishedAt: new Date() },\n }).catch(() => { });\n },\n GENERATE_TEST_PLAN: async (jobId: string, _config: any) => {\n logger.error({ jobId }, '[job-worker] GENERATE_TEST_PLAN claimed by worker \u2014 API did not process it in-process');\n await prismaAdmin.platformJob.update({\n where: { id: jobId },\n data: { status: 'FAILED', error: 'Test plan generation was not completed by the API \u2014 please retry from the UI.', finishedAt: new Date() },\n }).catch(() => { });\n },\n};\n\nasync function processRepoScanJob(jobId: string, config: any) {\n const { repoConnectionId, tenantId, projectId } = config;\n const jobStart = new Date();\n try {\n await updateJobProgress(tenantId, jobId, 5, 'Starting repo scan');\n\n const {\n fetchRepoTree, fetchFileContent, detectFramework,\n extractTestIds, extractStyleProfile,\n buildPageObjectInventory, resolveFixtureAliasesFromImports,\n } = await import('@detiq/agents');\n\n const conn = await withTenant(tenantId, (tx) =>\n tx.gitHubRepoConnection.findFirst({ where: { id: repoConnectionId, tenantId } })\n ) as any;\n if (!conn) throw new Error(`RepoConnection ${repoConnectionId} not found`);\n\n // Decrypt token\n const { decrypt } = await import('@detiq/core');\n const token = decrypt(conn.tokenEnc);\n\n // Mark scanning\n await withTenant(tenantId, (tx) =>\n tx.gitHubRepoConnection.update({ where: { id: repoConnectionId }, data: { scanStatus: 'SCANNING', scanError: null } })\n );\n\n await updateJobProgress(tenantId, jobId, 15, 'Fetching repo tree');\n // Use HEAD so it always resolves to the actual default branch regardless of stored value\n const treeResult = await fetchRepoTree(token, conn.repoFullName, 'HEAD');\n if (treeResult.truncated) {\n console.warn(`[repo-scan] ${conn.repoFullName}: tree truncated (>100k blobs) \u2014 detection may be incomplete`);\n await withTenant(tenantId, (tx) =>\n tx.gitHubRepoConnection.update({\n where: { id: repoConnectionId },\n data: { scanError: 'Repository tree was truncated by GitHub (>100k files) \u2014 some test files in deep subdirectories may be missing from this scan.' },\n })\n );\n }\n\n const frameworks = detectFramework(treeResult.paths);\n const primary = frameworks[0];\n await updateJobProgress(tenantId, jobId, 30, `Detected framework: ${primary?.framework ?? 'unknown'}`);\n\n // Prefer framework-detected glob when stored glob is the TS default (never explicitly set)\n const storedGlob = conn.testGlob || '';\n const testGlob = (storedGlob && storedGlob !== '**/*.spec.ts')\n ? storedGlob\n : (primary?.testGlob || storedGlob || '**/*.spec.ts');\n const framework = primary?.framework ?? 'unknown';\n const language = primary?.language ?? 'typescript';\n\n // Filter test files\n const { minimatch } = await import('minimatch').catch(() => ({ minimatch: null }));\n const isPythonTestFile = (p: string) =>\n p.endsWith('.py') && !p.endsWith('__init__.py') && !p.endsWith('conftest.py') &&\n (p.endsWith('_test.py') || /\\/test_[^/]+\\.py$/.test(p) || /^test_[^/]+\\.py$/.test(p) ||\n p.includes('/tests/') || p.includes('/test/'));\n // Java: testGlob matches all *.java \u2014 further filter to actual test classes only\n // (excludes helpers, base classes, utilities that don't follow *Test.java/*Tests.java naming)\n const isJavaTestFile = (p: string) =>\n p.endsWith('.java') && (\n /[A-Z]\\w*Tests?\\.java$/.test(p) ||\n (/\\/tests?\\//.test(p) && /[A-Z][a-z]/.test(p.split('/').pop() ?? ''))\n );\n // Build list of globs to try \u2014 primary + common variants so we don't miss .test.ts alongside .spec.ts\n const testGlobs = minimatch\n ? Array.from(new Set([\n testGlob,\n ...(testGlob === '**/*.spec.ts' ? ['**/*.test.ts', '**/*.cy.ts'] : []),\n ...(testGlob === '**/*.spec.js' ? ['**/*.test.js', '**/*.cy.js'] : []),\n ]))\n : [];\n const testPaths = treeResult.paths.filter((p) => {\n if (language === 'java') return isJavaTestFile(p);\n if (language === 'python') return isPythonTestFile(p);\n if (testGlobs.length) return testGlobs.some(g => (minimatch as any)(p, g, { matchBase: true }));\n return p.endsWith('.spec.ts') || p.endsWith('.spec.js') || p.endsWith('.test.ts') || p.endsWith('.test.js') || p.endsWith('.cy.ts') || p.endsWith('.cy.js') || p.endsWith('_test.py') || p.includes('/test_');\n }).slice(0, 2000); // cap at 2000 test files\n\n await updateJobProgress(tenantId, jobId, 40, `Found ${testPaths.length} test files`);\n\n if (testPaths.length === 0) {\n logger.warn({ jobId, repoConnectionId, framework }, '[repo-scan] no test files matched glob \u2014 scan complete with 0 results');\n await withTenant(tenantId, (tx) =>\n tx.gitHubRepoConnection.update({\n where: { id: repoConnectionId },\n data: { scanStatus: 'DONE', testFileCount: 0, extractedIdCount: 0, lastScannedAt: new Date(), lastSyncAt: new Date() },\n })\n );\n await updateJobProgress(tenantId, jobId, 100, 'Done: 0 test files found');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', finishedAt: new Date(), result: { testFileCount: 0, extractedIdCount: 0 } as any } })\n );\n return;\n }\n\n // Extract test IDs from each file\n const allExtracted: any[] = [];\n const styleSampleFiles: Array<{ path: string; content: string }> = [];\n let processed = 0;\n\n // Delete old test files for this connection\n await withTenant(tenantId, (tx) => tx.repoTestFile.deleteMany({ where: { repoConnectionId } }));\n\n for (const filePath of testPaths) {\n const content = await fetchFileContent(token, conn.repoFullName, filePath);\n if (!content) continue;\n\n // G-07: multi-file fixture tracing \u2014 resolve non-obvious aliases before extraction\n const extraAliases = await resolveFixtureAliasesFromImports(\n content, token, conn.repoFullName, filePath,\n ).catch(() => new Set<string>());\n const extracted = extractTestIds(content, filePath, framework, extraAliases);\n allExtracted.push(...extracted);\n\n await withTenant(tenantId, (tx) =>\n tx.repoTestFile.create({\n data: {\n tenantId,\n projectId,\n repoConnectionId,\n filePath,\n framework,\n extractedIds: extracted.map((e) => ({ name: e.name, normalizedName: e.normalizedName, lineNumber: e.lineNumber })) as any,\n rawSample: content.slice(0, 2000),\n lastScannedAt: new Date(),\n },\n })\n );\n\n if (styleSampleFiles.length < 5) styleSampleFiles.push({ path: filePath, content: content.slice(0, 3000) });\n processed++;\n\n if (processed % 10 === 0) {\n await updateJobProgress(tenantId, jobId, 40 + Math.floor((processed / testPaths.length) * 40), `Processed ${processed}/${testPaths.length} files`);\n }\n }\n\n // Fetch and parse framework config files (playwright.config.ts, cypress.config.ts, tsconfig.json)\n // Fetch and parse framework config files for all supported frameworks.\n // Pure regex/JSON \u2014 no LLM. Results override LLM inference for path detection.\n // Covers: playwright, cypress, jest, pytest, junit/maven/gradle, selenium-java/py,\n // webdriverio, cucumber \u2014 all frameworks in DetectedFramework.\n const configPaths: {\n testDir?: string; fixturesFolder?: string; baseURL?: string;\n tsconfigPaths?: Record<string, string[]>; tsconfigBaseUrl?: string;\n javaTestSourceDir?: string; jestModuleNameMapper?: Record<string, string>;\n } = {};\n try {\n const pathSet = new Set(treeResult.paths as string[]);\n const lang = framework.includes('java') ? 'java'\n : (framework.includes('py') || framework === 'pytest') ? 'python' : 'js';\n\n const configFilesToFetch: Array<{ path: string; type: string; dirPrefix: string }> = [];\n const tryAdd = (candidates: string[], type: string) => {\n for (const c of candidates) { if (pathSet.has(c)) { configFilesToFetch.push({ path: c, type, dirPrefix: '' }); break; } }\n };\n // Monorepo fallback: when root-level config is absent, find shallowest match anywhere in tree.\n // The dirPrefix (e.g. 'packages/e2e/') is prepended to testDir so paths are repo-root-relative.\n const scanTree = (pattern: RegExp, type: string) => {\n if (configFilesToFetch.some(f => f.type === type)) return;\n const all = (treeResult.paths as string[]).filter(p => pattern.test(p)).sort((a, b) => a.split('/').length - b.split('/').length);\n if (all[0]) {\n const lastSlash = all[0].lastIndexOf('/');\n const dirPrefix = lastSlash > 0 ? all[0].slice(0, lastSlash + 1) : '';\n configFilesToFetch.push({ path: all[0], type, dirPrefix });\n }\n };\n\n if (lang === 'js') {\n if (framework.startsWith('playwright')) {\n tryAdd(['playwright.config.ts', 'playwright.config.js'], 'playwright');\n scanTree(/(?:^|\\/)playwright\\.config\\.(ts|js|mts)$/, 'playwright');\n }\n if (framework.startsWith('cypress')) {\n tryAdd(['cypress.config.ts', 'cypress.config.js'], 'cypress');\n scanTree(/(?:^|\\/)cypress\\.config\\.(ts|js)$/, 'cypress');\n }\n if (framework === 'jest-ts' || framework === 'jest-js') {\n tryAdd(['jest.config.ts', 'jest.config.js', 'jest.config.mjs'], 'jest');\n scanTree(/(?:^|\\/)jest\\.config\\.(ts|js|mjs|cjs)$/, 'jest');\n tryAdd(['package.json'], 'package-json');\n }\n if (framework === 'webdriverio') {\n tryAdd(['wdio.conf.ts', 'wdio.conf.js', 'wdio.config.ts', 'wdio.config.js'], 'wdio');\n scanTree(/(?:^|\\/)wdio\\.(?:conf|config)\\.(ts|js|mjs)$/, 'wdio');\n }\n if (framework === 'cucumber' || framework === 'gherkin') {\n tryAdd(['.cucumber.js', '.cucumber.cjs', 'cucumber.json', '.cucumber.yaml', '.cucumber.yml'], 'cucumber');\n tryAdd(['package.json'], 'package-json');\n }\n tryAdd(['tsconfig.json'], 'tsconfig');\n scanTree(/(?:^|\\/)tsconfig(?:\\.base)?\\.json$/, 'tsconfig');\n } else if (lang === 'python') {\n tryAdd(['pytest.ini'], 'pytest-ini');\n scanTree(/(?:^|\\/)pytest\\.ini$/, 'pytest-ini');\n tryAdd(['pyproject.toml'], 'pyproject-toml');\n scanTree(/(?:^|\\/)pyproject\\.toml$/, 'pyproject-toml');\n tryAdd(['setup.cfg'], 'setup-cfg');\n } else if (lang === 'java') {\n tryAdd(['pom.xml'], 'pom-xml');\n scanTree(/(?:^|\\/)pom\\.xml$/, 'pom-xml');\n tryAdd(['build.gradle.kts', 'build.gradle'], 'build-gradle');\n scanTree(/(?:^|\\/)build\\.gradle(?:\\.kts)?$/, 'build-gradle');\n tryAdd(['testng.xml', 'src/test/resources/testng.xml'], 'testng-xml');\n }\n\n // Inline parsers \u2014 mirror parseFrameworkConfig from @detiq/agents (kept inline to avoid build dep)\n const normDir = (s: string) => s.replace(/^\\.\\//, '').replace(/\\/$/, '');\n const globRoot = (pattern: string): string | null => {\n const root = pattern.replace(/\\/\\*\\*.*$/, '').replace(/\\/\\*[^*].*$/, '');\n return (root && !root.startsWith('*') && !root.startsWith('{')) ? normDir(root) : null;\n };\n\n const fetched = await Promise.all(\n configFilesToFetch.map(async ({ path: cfgPath, type, dirPrefix }) => {\n const content = await fetchFileContent(token, conn.repoFullName, cfgPath).catch(() => null);\n if (!content) return null;\n const r: typeof configPaths = {};\n\n if (type === 'playwright') {\n const m = content.match(/\\btestDir\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (m) r.testDir = normDir(m[1]);\n const bm = content.match(/\\bbaseURL\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (bm) r.baseURL = bm[1];\n } else if (type === 'cypress') {\n const sm = content.match(/\\bspecPattern\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (sm) { const gr = globRoot(sm[1]); if (gr) r.testDir = gr; }\n const fm = content.match(/\\bfixturesFolder\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (fm) r.fixturesFolder = normDir(fm[1]);\n const bm = content.match(/\\bbaseUrl\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (bm) r.baseURL = bm[1];\n } else if (type === 'jest') {\n const rm = content.match(/\\broots\\s*:\\s*\\[([^\\]]+)\\]/);\n if (rm) { const fr = rm[1].match(/['\"`]([^'\"`]+)['\"`]/)?.[1]; if (fr) r.testDir = fr.replace(/<rootDir>\\/?/, '').replace(/\\/$/, '') || 'src'; }\n if (!r.testDir) { const tm = content.match(/\\btestMatch\\s*:\\s*\\[([^\\]]+)\\]/); if (tm) { const fp = tm[1].match(/['\"`]([^'\"`]+)['\"`]/)?.[1]; if (fp) { const gr = globRoot(fp.replace(/<rootDir>\\/?/, '')); if (gr) r.testDir = gr; } } }\n const mm = content.match(/\\bmoduleNameMapper\\s*:\\s*\\{([^}]+)\\}/s);\n if (mm) { const map: Record<string, string> = {}; for (const [, k, v] of mm[1].matchAll(/['\"`]([^'\"`]+)['\"`]\\s*:\\s*['\"`]([^'\"`]+)['\"`]/g)) map[k] = v; if (Object.keys(map).length) r.jestModuleNameMapper = map; }\n } else if (type === 'package-json') {\n try { const p = JSON.parse(content) as any; const j = p?.jest ?? {}; if (Array.isArray(j.roots) && j.roots[0]) r.testDir = String(j.roots[0]).replace(/<rootDir>\\/?/, '').replace(/\\/$/, '') || 'src'; if (!r.testDir && Array.isArray(j.testMatch) && j.testMatch[0]) { const gr = globRoot(String(j.testMatch[0]).replace(/<rootDir>\\/?/, '')); if (gr) r.testDir = gr; } if (j.moduleNameMapper && typeof j.moduleNameMapper === 'object') r.jestModuleNameMapper = j.moduleNameMapper; } catch { /* ignore */ }\n } else if (type === 'tsconfig') {\n try { const stripped = content.replace(/\\/\\/[^\\n]*/g, '').replace(/,(\\s*[}\\]])/g, '$1'); const p = JSON.parse(stripped) as any; const opts = p?.compilerOptions ?? {}; if (opts.paths && typeof opts.paths === 'object') r.tsconfigPaths = opts.paths; if (typeof opts.baseUrl === 'string') r.tsconfigBaseUrl = opts.baseUrl; } catch { /* ignore */ }\n } else if (type === 'pytest-ini') {\n const m = content.match(/^\\s*testpaths\\s*=\\s*(.+)$/m);\n if (m) { const first = m[1].trim().split(/\\s+/)[0]; if (first) r.testDir = normDir(first); }\n } else if (type === 'pyproject-toml') {\n const m = content.match(/testpaths\\s*=\\s*\\[([^\\]]+)\\]/);\n if (m) { const first = m[1].match(/['\"]([^'\"]+)['\"]/)?.[1]; if (first) r.testDir = normDir(first); }\n if (!r.testDir) { const m2 = content.match(/testpaths\\s*=\\s*['\"]([^'\"]+)['\"]/); if (m2) r.testDir = normDir(m2[1]); }\n } else if (type === 'setup-cfg') {\n const m = content.match(/^\\s*testpaths\\s*=\\s*(.+)$/m);\n if (m) { const first = m[1].trim().split(/[\\s,]+/)[0]; if (first) r.testDir = normDir(first); }\n } else if (type === 'pom-xml') {\n const m = content.match(/<testSourceDirectory>\\s*([^<]+)\\s*<\\/testSourceDirectory>/);\n if (m) r.javaTestSourceDir = normDir(m[1].trim());\n } else if (type === 'build-gradle') {\n const m = content.match(/srcDir[s]?\\s*[=([]?\\s*['\"`]([^'\"`]+)['\"`]/) ?? content.match(/srcDir\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/);\n if (m) r.javaTestSourceDir = normDir(m[1]);\n } else if (type === 'wdio') {\n const sm = content.match(/\\bspecs\\s*:\\s*\\[([^\\]]+)\\]/);\n if (sm) { const fp = sm[1].match(/['\"`]([^'\"`]+)['\"`]/)?.[1]; if (fp) { const gr = globRoot(fp); if (gr) r.testDir = gr; } }\n const bm = content.match(/\\bbaseUrl\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (bm) r.baseURL = bm[1];\n } else if (type === 'cucumber') {\n const pm = content.match(/\\bpaths\\s*:\\s*\\[([^\\]]+)\\]/);\n if (pm) { const fp = pm[1].match(/['\"`]([^'\"`]+)['\"`]/)?.[1]; if (fp) { const gr = globRoot(fp); if (gr) r.testDir = gr; } }\n if (!r.testDir) { try { const p = JSON.parse(content) as any; const paths = p?.default?.paths ?? p?.paths; if (Array.isArray(paths) && paths[0]) { const gr = globRoot(String(paths[0])); if (gr) r.testDir = gr; } } catch { /* not JSON */ } }\n }\n // Monorepo workspace prefix: config found at e.g. packages/frontend/playwright.config.ts.\n // testDir from config is relative to that file ('tests' or 'e2e'). Prepend the directory\n // so the final path is repo-root-relative ('packages/frontend/tests').\n if (dirPrefix && r.testDir && !r.testDir.startsWith(dirPrefix)) {\n r.testDir = dirPrefix + r.testDir;\n }\n if (dirPrefix && r.javaTestSourceDir && !r.javaTestSourceDir.startsWith(dirPrefix)) {\n r.javaTestSourceDir = dirPrefix + r.javaTestSourceDir;\n }\n return r;\n })\n );\n\n for (const parsed of fetched) {\n if (!parsed) continue;\n if (parsed.testDir && !configPaths.testDir) configPaths.testDir = parsed.testDir;\n if (parsed.fixturesFolder && !configPaths.fixturesFolder) configPaths.fixturesFolder = parsed.fixturesFolder;\n if (parsed.baseURL && !configPaths.baseURL) configPaths.baseURL = parsed.baseURL;\n if (parsed.tsconfigPaths && !configPaths.tsconfigPaths) configPaths.tsconfigPaths = parsed.tsconfigPaths;\n if (parsed.tsconfigBaseUrl && !configPaths.tsconfigBaseUrl) configPaths.tsconfigBaseUrl = parsed.tsconfigBaseUrl;\n if (parsed.javaTestSourceDir && !configPaths.javaTestSourceDir) configPaths.javaTestSourceDir = parsed.javaTestSourceDir;\n if (parsed.jestModuleNameMapper && !configPaths.jestModuleNameMapper) configPaths.jestModuleNameMapper = parsed.jestModuleNameMapper;\n }\n } catch { /* non-fatal */ }\n\n // Extract style profile for aligned generation\n // For Java, include main source paths so LLM can detect page object location in src/main/java/\n // Fall back to existing styleProfile from DB if extraction fails or returns null.\n let styleProfile: any = conn.styleProfile ?? null;\n if (styleSampleFiles.length > 0) {\n try {\n const mainSourcePaths = language === 'java'\n ? treeResult.paths.filter((p: string) => /^src\\/main\\/java\\//.test(p)).slice(0, 50)\n : [];\n // For TS/JS, also include non-test source files so LLM can detect pageObjectLocation\n // (e.g. src/advantage/pages/ in playwright-sample-project)\n const testPathSet = new Set(testPaths);\n const sourceFilePaths = (language === 'typescript' || language === 'javascript')\n ? treeResult.paths\n .filter((p: string) =>\n (p.endsWith('.ts') || p.endsWith('.js')) &&\n !testPathSet.has(p) &&\n !p.endsWith('.d.ts') &&\n !p.includes('node_modules/') &&\n !p.includes('dist/') &&\n !p.includes('.config.'))\n .slice(0, 60)\n : [];\n const allPathsForProfile = [...testPaths, ...mainSourcePaths, ...sourceFilePaths].slice(0, 200);\n // Pass full tree paths separately so detectDataDirs can find testdata/*.json, fixtures/*.yaml etc.\n const freshProfile = await extractStyleProfile(styleSampleFiles, framework, language, tenantId, projectId, jobId, allPathsForProfile, treeResult.paths);\n if (freshProfile) styleProfile = freshProfile;\n } catch { /* non-fatal */ }\n }\n\n // Merge config file ground-truth into styleProfile (Priority 0 \u2014 highest, no LLM inference needed).\n // Java: javaTestSourceDir also sets testLocation (equivalent field for Java).\n if (styleProfile && Object.keys(configPaths).length > 0) {\n if (configPaths.testDir) styleProfile.configTestDir = configPaths.testDir;\n if (configPaths.javaTestSourceDir) {\n styleProfile.configTestDir = configPaths.javaTestSourceDir;\n // Also set testLocation if not already present from LLM scan\n if (!styleProfile.testLocation) styleProfile.testLocation = configPaths.javaTestSourceDir;\n }\n if (configPaths.fixturesFolder) styleProfile.configFixturesFolder = configPaths.fixturesFolder;\n if (configPaths.baseURL) styleProfile.configBaseURL = configPaths.baseURL;\n if (configPaths.tsconfigPaths) styleProfile.tsconfigPaths = configPaths.tsconfigPaths;\n if (configPaths.tsconfigBaseUrl) styleProfile.tsconfigBaseUrl = configPaths.tsconfigBaseUrl;\n if (configPaths.jestModuleNameMapper) styleProfile.jestModuleNameMapper = configPaths.jestModuleNameMapper;\n }\n\n // Extract page object inventory \u2014 finds POM/page classes in non-test files\n // Fall back to existing inventory from DB if fetch fails.\n let pageObjectInventory: any[] = (conn.pageObjectInventory as any[]) ?? [];\n try {\n const freshInventory = await buildPageObjectInventory(token, conn.repoFullName, treeResult.paths, framework);\n if (freshInventory.length > 0) {\n pageObjectInventory = freshInventory;\n await updateJobProgress(tenantId, jobId, 82, `Found ${pageObjectInventory.length} page objects`);\n }\n } catch { /* non-fatal */ }\n\n // Run coverage mapping\n await updateJobProgress(tenantId, jobId, 85, 'Running coverage mapping');\n const { buildCoverageMap } = await import('@detiq/agents');\n const testCases = await withTenant(tenantId, (tx) =>\n tx.testCase.findMany({ where: { projectId, tenantId, status: { not: 'RETIRED' } }, select: { id: true, title: true, ref: true } })\n ) as any[];\n\n const mappings = await buildCoverageMap(testCases, allExtracted, tenantId);\n\n // Upsert CoverageMapping records (skip manualOverride=true)\n for (const m of mappings) {\n const existing = await withTenant(tenantId, (tx) =>\n tx.coverageMapping.findUnique({ where: { testCaseId: m.testCaseId } })\n ) as any;\n if (existing?.manualOverride) continue;\n\n await withTenant(tenantId, (tx) =>\n tx.coverageMapping.upsert({\n where: { testCaseId: m.testCaseId },\n create: { tenantId, projectId, testCaseId: m.testCaseId, repoConnectionId, status: m.status, matchedFilePath: m.matchedFilePath ?? null, scriptTestName: m.scriptTestName ?? null, lineNumber: m.lineNumber ?? null, confidence: m.confidence, matchMethod: m.matchMethod },\n update: { status: m.status, matchedFilePath: m.matchedFilePath ?? null, scriptTestName: m.scriptTestName ?? null, lineNumber: m.lineNumber ?? null, confidence: m.confidence, matchMethod: m.matchMethod },\n })\n );\n }\n\n const mapped = mappings.filter((m) => m.status === 'MAPPED').length;\n const unmapped = mappings.filter((m) => m.status === 'UNMAPPED').length;\n const ambiguous = mappings.filter((m) => m.status === 'AMBIGUOUS').length;\n const coveragePercent = testCases.length > 0 ? (mapped / testCases.length) * 100 : 0;\n\n // Write coverage snapshot\n await withTenant(tenantId, (tx) =>\n tx.coverageSnapshot.create({\n data: { tenantId, projectId, totalCases: testCases.length, mappedCases: mapped, unmappedCases: unmapped, ambiguousCases: ambiguous, coveragePercent },\n })\n );\n\n // For Java, if styleProfile still missing testStructurePattern, infer two-tier from tree paths.\n // This covers the case where both extractStyleProfile and buildPageObjectInventory failed.\n const resolvedStyleProfile: any = (() => {\n const sp = styleProfile ?? {};\n if (framework === 'selenium-java' && !sp.testStructurePattern) {\n const hasMavenMain = treeResult.paths.some((p: string) => /^src\\/main\\/java\\//.test(p));\n if (hasMavenMain) return { ...sp, testStructurePattern: 'two-tier' };\n }\n return sp;\n })();\n\n // Update repo connection with results\n await withTenant(tenantId, (tx) =>\n tx.gitHubRepoConnection.update({\n where: { id: repoConnectionId },\n data: {\n scanStatus: 'DONE',\n detectedFramework: framework,\n configFilePath: primary?.configPath ?? null,\n testGlob: testGlob,\n testFileCount: testPaths.length,\n extractedIdCount: allExtracted.length,\n styleProfile: resolvedStyleProfile as any,\n pageObjectInventory: pageObjectInventory.length > 0 ? pageObjectInventory as any : undefined,\n treeSnapshot: JSON.stringify(treeResult.paths).slice(0, 100000),\n lastScannedAt: new Date(),\n lastSyncAt: new Date(),\n },\n })\n );\n try {\n const { generateRepoSkillFiles } = await import('@detiq/agents');\n await generateRepoSkillFiles(tenantId, projectId, {\n repoFullName: conn.repoFullName,\n framework,\n language,\n testGlob,\n styleProfile: resolvedStyleProfile,\n pageObjectInventory,\n testPaths,\n extractedTestCount: allExtracted.length,\n treeWasTruncated: treeResult.truncated,\n extractedTests: allExtracted,\n });\n } catch (e: any) {\n logger.warn({ jobId, repoConnectionId, err: e?.message }, '[repo-scan] repo skill files generation failed (non-fatal)');\n }\n try {\n const { alignSkillFile } = await import('@detiq/agents');\n await alignSkillFile(tenantId, projectId, {\n framework,\n language,\n styleProfile: resolvedStyleProfile,\n repoFullName: conn.repoFullName,\n });\n } catch (e: any) {\n logger.warn({ jobId, repoConnectionId, err: e?.message }, '[repo-scan] alignSkillFile failed (non-fatal)');\n }\n\n await updateJobProgress(tenantId, jobId, 100, `Done: ${mapped} mapped, ${unmapped} unmapped`);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', finishedAt: new Date(), result: { testFileCount: testPaths.length, extractedIdCount: allExtracted.length, mapped, unmapped, ambiguous, coveragePercent } as any } })\n );\n aggregateJobTokens(tenantId, jobId, projectId, jobStart).catch(console.error);\n } catch (err: any) {\n const cause = err?.cause?.message ?? (typeof err?.cause === 'string' ? err.cause : '');\n const msg = `${String(err?.message ?? err)}${cause ? ` \u2014 ${cause}` : ''}`;\n logger.error({ jobId, repoConnectionId, err: msg }, '[repo-scan] failed');\n await withTenant(tenantId, (tx) =>\n tx.gitHubRepoConnection.update({ where: { id: repoConnectionId }, data: { scanStatus: 'FAILED', scanError: msg.slice(0, 500) } })\n ).catch(() => { });\n await recordJobFailure(tenantId, jobId, err);\n }\n}\n\n// \u2500\u2500 BATCH_GENERATE_ALIGNED \u2014 generate framework-aligned scripts for UNMAPPED cases \u2500\u2500\n\nasync function processBatchGenerateAlignedJob(jobId: string, config: any) {\n const { tenantId, projectId, repoConnectionId, scope = 'UNMAPPED', testCaseIds, force = false } = config;\n try {\n await updateJobProgress(tenantId, jobId, 5, 'Loading repo connection');\n\n const conn = await withTenant(tenantId, (tx) =>\n (tx as any).gitHubRepoConnection.findFirst({ where: { id: repoConnectionId, tenantId } })\n ) as any;\n if (!conn) throw new Error(`RepoConnection ${repoConnectionId} not found`);\n\n const {\n generateScriptAligned, deriveRepoTargetPath, validateGeneratedScript,\n mapWithConcurrency, SCRIPT_GEN_CONCURRENCY,\n } = await import('@detiq/agents');\n\n const styleProfile = conn.styleProfile as any;\n const pageObjectInventory = (conn.pageObjectInventory as any[]) ?? [];\n const framework = conn.detectedFramework ?? 'playwright-ts';\n const language = styleProfile?.language ?? 'typescript';\n\n // Resolve which test cases to process\n let caseIds: string[];\n if (testCaseIds && testCaseIds.length > 0) {\n caseIds = testCaseIds;\n } else if (scope === 'UNMAPPED') {\n const mappings = await withTenant(tenantId, (tx) =>\n (tx as any).coverageMapping.findMany({\n where: { tenantId, projectId, repoConnectionId, status: 'UNMAPPED' },\n select: { testCaseId: true },\n })\n ) as any[];\n caseIds = mappings.map((m: any) => m.testCaseId);\n } else {\n const cases = await withTenant(tenantId, (tx) =>\n tx.testCase.findMany({\n where: { tenantId, projectId, status: { not: 'RETIRED' } },\n select: { id: true },\n })\n ) as any[];\n caseIds = cases.map((c: any) => c.id);\n }\n\n await updateJobProgress(tenantId, jobId, 15, `Generating scripts for ${caseIds.length} test cases`);\n\n let done = 0;\n const errors: string[] = [];\n\n // One LLM call per case \u2014 bounded worker pool instead of a strictly serial loop,\n // which made large batches take an hour or more.\n await mapWithConcurrency(caseIds, SCRIPT_GEN_CONCURRENCY, async (tcId: string) => {\n try {\n // Skip if script already exists and not forcing\n if (!force) {\n const existing = await withTenant(tenantId, (tx) =>\n (tx as any).testScript.findFirst({ where: { testCaseId: tcId } })\n );\n if (existing) { done++; return; }\n }\n\n // Get screen URL and ref for target path derivation\n const tc = await withTenant(tenantId, (tx) =>\n tx.testCase.findUnique({\n where: { id: tcId },\n select: { id: true, ref: true, title: true, screen: { select: { url: true } } },\n })\n );\n\n const screenUrl = (tc as any)?.screen?.url ?? '';\n const repoTargetPath = styleProfile\n ? deriveRepoTargetPath(screenUrl, styleProfile, framework)\n : deriveFallbackTargetPath((tc as any)?.ref ?? (tc as any)?.title ?? tcId, framework);\n\n const result = await generateScriptAligned({\n tenantId, projectId, testCaseId: tcId,\n styleProfile,\n pageObjectInventory,\n repoTargetPath,\n save: true,\n });\n\n // Validate the generated script\n if (result.code) {\n const validation = await validateGeneratedScript(result.code, framework, language);\n await withTenant(tenantId, (tx) =>\n (tx as any).testScript.updateMany({\n where: { testCaseId: tcId },\n data: {\n validationStatus: validation.ok ? 'PASS' : 'FAIL',\n validationErrors: validation.ok ? null : validation.errors.slice(0, 2000),\n },\n })\n );\n }\n\n done++;\n } catch (e: any) {\n errors.push(`${tcId}: ${String(e.message).slice(0, 100)}`);\n }\n\n if (done % 5 === 0) {\n await updateJobProgress(tenantId, jobId, 15 + Math.floor((done / caseIds.length) * 80), `Generated ${done}/${caseIds.length}`);\n }\n });\n\n refreshTestScriptSkillFile(tenantId, projectId).catch(console.error);\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', finishedAt: new Date(), result: { done, errors: errors.slice(0, 20), total: caseIds.length } as any },\n })\n );\n } catch (err: any) {\n const msg = String(err?.message ?? err);\n logger.error({ jobId, err: msg }, '[batch-generate-aligned] failed');\n await recordJobFailure(tenantId, jobId, err);\n }\n}\n\n// \u2500\u2500 CREATE_PR \u2014 push generated scripts to GitHub as a PR \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function processCreatePRJob(jobId: string, config: any) {\n const { tenantId, projectId, repoConnectionId, testScriptIds, crawlJobId } = config;\n try {\n await updateJobProgress(tenantId, jobId, 5, 'Loading connection');\n\n const conn = await withTenant(tenantId, (tx) =>\n (tx as any).gitHubRepoConnection.findFirst({ where: { id: repoConnectionId, tenantId } })\n ) as any;\n if (!conn) throw new Error(`RepoConnection ${repoConnectionId} not found`);\n\n const { decrypt } = await import('@detiq/core');\n const token = decrypt(conn.tokenEnc);\n\n const { pushScriptsAsPR, getFileContent, mergeSpecContent } = await import('@detiq/agents');\n\n const framework = conn.detectedFramework ?? 'playwright-ts';\n\n // Collect scripts + their target paths\n let scripts: any[];\n if (testScriptIds && testScriptIds.length > 0) {\n scripts = await withTenant(tenantId, (tx) =>\n (tx as any).testScript.findMany({\n where: { id: { in: testScriptIds }, tenantId, code: { not: null } },\n include: { testCase: { select: { title: true, ref: true, repoTargetPath: true } } },\n })\n );\n } else {\n // All scripts that haven't been pushed yet\n scripts = await withTenant(tenantId, (tx) =>\n (tx as any).testScript.findMany({\n where: { tenantId, projectId, pushedToPR: false, validationStatus: { in: ['PASS', 'PENDING'] }, code: { not: null } },\n include: { testCase: { select: { title: true, ref: true, repoTargetPath: true } } },\n })\n );\n }\n\n if (scripts.length === 0) throw new Error('No generated scripts found to push.');\n\n // For each file, check if it already exists in the repo and merge if so\n const files = await Promise.all(scripts.map(async (s: any) => {\n const path = s.testCase?.repoTargetPath\n ?? deriveFallbackTargetPath(s.testCase?.ref ?? s.testCase?.title ?? s.id, framework);\n let content = s.code;\n\n // Spec files: attempt to merge with existing content\n const isSpecFile = /\\.(spec|test|cy)\\.(ts|js|py|java)$/.test(path) || /_test\\.py$/.test(path);\n if (isSpecFile) {\n const existing = await getFileContent(token, conn.repoFullName, path, conn.defaultBranch).catch(() => null);\n if (existing && existing.trim().length > 0) {\n logger.info({ path }, '[create-pr] merging new tests into existing spec file');\n content = mergeSpecContent(existing, s.code);\n }\n }\n\n return { path, content };\n }));\n\n if (files.length === 0) throw new Error('No scripts to push.');\n\n await updateJobProgress(tenantId, jobId, 20, `Pushing ${files.length} scripts to GitHub`);\n\n const now = new Date().toISOString().slice(0, 10);\n const branchName = `zeta/new-coverage-${now}-${jobId.slice(0, 8)}`;\n\n const prBody = buildPRBody(scripts, projectId);\n\n const prResult = await pushScriptsAsPR({\n token,\n repo: conn.repoFullName,\n baseBranch: conn.defaultBranch,\n branchName,\n files,\n prTitle: `ZeTa: Add test coverage for ${files.length} new scenario${files.length === 1 ? '' : 's'}`,\n prBody,\n draft: false,\n });\n\n // Record the PR\n const pr = await withTenant(tenantId, (tx) =>\n (tx as any).gitHubPR.create({\n data: {\n tenantId, projectId,\n repoConnectionId,\n prNumber: prResult.prNumber,\n prUrl: prResult.prUrl,\n branch: prResult.branch,\n headSha: prResult.headSha,\n status: 'OPEN',\n crawlJobId: crawlJobId ?? null,\n scriptCount: files.length,\n prBody: prBody.slice(0, 10000),\n },\n })\n ) as any;\n\n // Mark scripts as pushed\n const scriptIds = scripts.map((s: any) => s.id);\n await withTenant(tenantId, (tx) =>\n (tx as any).testScript.updateMany({\n where: { id: { in: scriptIds } },\n data: { pushedToPR: true, pushedPRId: pr.id },\n })\n );\n\n await notifyAdmins(tenantId, {\n type: 'GITHUB_PR_CREATED',\n title: 'ZeTa created a GitHub PR',\n body: `${files.length} new test script${files.length === 1 ? '' : 's'} pushed. PR #${prResult.prNumber}: ${prResult.prUrl}`,\n metadata: { prUrl: prResult.prUrl, prNumber: prResult.prNumber, scriptCount: files.length },\n });\n\n await updateJobProgress(tenantId, jobId, 100, `PR #${prResult.prNumber} created`);\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', finishedAt: new Date(), result: { prUrl: prResult.prUrl, prNumber: prResult.prNumber, scriptCount: files.length } as any },\n })\n );\n } catch (err: any) {\n const msg = String(err?.message ?? err);\n logger.error({ jobId, err: msg }, '[create-pr] failed');\n await recordJobFailure(tenantId, jobId, err);\n }\n}\n\nfunction buildPRBody(scripts: any[], projectId: string): string {\n const lines = [\n '## ZeTa \u2014 Automated Test Coverage',\n '',\n `Generated by ZeTa Quality Intelligence Platform for project \\`${projectId}\\`.`,\n '',\n '### Scripts Added',\n '',\n '| Test Case | Target Path |',\n '|---|---|',\n ];\n for (const s of scripts.slice(0, 30)) {\n const title = s.testCase?.title ?? s.id;\n const path = s.testCase?.repoTargetPath ?? '(unknown)';\n lines.push(`| ${title.replace(/\\|/g, '\\\\|').slice(0, 60)} | \\`${path}\\` |`);\n }\n if (scripts.length > 30) lines.push(`| ... and ${scripts.length - 30} more | |`);\n lines.push('', '---', '_Review each test carefully before merging. ZeTa generates scripts aligned to your existing framework conventions._');\n return lines.join('\\n');\n}\n\n// \u2500\u2500 NEW_PAGES_PIPELINE \u2014 post-crawl: detect new screens, generate tests + scripts \u2500\u2500\n\nasync function processNewPagesPipelineJob(jobId: string, config: any) {\n const { tenantId, projectId, crawlJobId: _crawlJobId, repoConnectionId, autoPR = false } = config;\n try {\n await updateJobProgress(tenantId, jobId, 5, 'Detecting new screens');\n\n // Find repo connection (use provided or find first active)\n const conn = await withTenant(tenantId, (tx) =>\n (tx as any).gitHubRepoConnection.findFirst({\n where: repoConnectionId\n ? { id: repoConnectionId, tenantId }\n : { tenantId, projectId, status: 'ACTIVE' },\n })\n ) as any;\n\n // Get all screens last seen after the lastSyncAt of the repo connection\n const sinceDate = conn?.lastSyncAt ?? new Date(0);\n const newScreens = await withTenant(tenantId, (tx) =>\n tx.screen.findMany({\n where: {\n tenantId,\n projectId,\n lastSeenAt: { gt: sinceDate },\n status: { not: 'DELETED' },\n },\n select: { id: true, url: true, name: true },\n take: 100,\n })\n ) as any[];\n\n if (newScreens.length === 0) {\n await updateJobProgress(tenantId, jobId, 100, 'No new screens to process');\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({ where: { id: jobId }, data: { status: 'DONE', finishedAt: new Date(), result: { newScreens: 0 } as any } })\n );\n return;\n }\n\n await updateJobProgress(tenantId, jobId, 15, `Found ${newScreens.length} new screens`);\n\n // For each new screen, enqueue GENERATE_TESTS if no test cases exist\n const generationJobs: string[] = [];\n for (const screen of newScreens) {\n const caseCount = await withTenant(tenantId, (tx) =>\n tx.testCase.count({ where: { tenantId, screenId: screen.id } })\n );\n if (caseCount === 0) {\n const jid = await enqueueJob('GENERATE_SCREEN_TESTS', {\n tenantId, projectId, screenId: screen.id,\n triggerAlignedScripts: conn != null,\n repoConnectionId: (conn as any)?.id ?? null,\n });\n generationJobs.push(jid);\n }\n }\n\n await updateJobProgress(tenantId, jobId, 60, `Queued ${generationJobs.length} test generation jobs`);\n\n // If repo connected and autoPR enabled, schedule BATCH_GENERATE_ALIGNED after generation\n if (conn && autoPR) {\n await enqueueJob('BATCH_GENERATE_ALIGNED', {\n tenantId, projectId,\n repoConnectionId: conn.id,\n scope: 'UNMAPPED',\n });\n }\n\n await withTenant(tenantId, (tx) =>\n tx.platformJob.update({\n where: { id: jobId },\n data: { status: 'DONE', finishedAt: new Date(), result: { newScreens: newScreens.length, generationJobs: generationJobs.length } as any },\n })\n );\n } catch (err: any) {\n const msg = String(err?.message ?? err);\n logger.error({ jobId, err: msg }, '[new-pages-pipeline] failed');\n await recordJobFailure(tenantId, jobId, err);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["FailureType", "refreshTestDataSkillFile", "decrypt", "path"]
|
|
7
7
|
}
|