@validate.qa/runner 1.0.41 → 1.0.43

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.
Files changed (3) hide show
  1. package/dist/cli.js +931 -132
  2. package/dist/cli.mjs +931 -132
  3. package/package.json +2 -2
package/dist/cli.mjs CHANGED
@@ -1588,6 +1588,9 @@ var init_types = __esm({
1588
1588
  });
1589
1589
 
1590
1590
  // ../shared/dist/constants.js
1591
+ function isEmailFromIdentity(value) {
1592
+ return typeof value === "string" && EMAIL_FROM_IDENTITIES.includes(value);
1593
+ }
1591
1594
  function isValidMobileAppId(appId) {
1592
1595
  return appId.length > 0 && appId.length <= MOBILE_APP_ID_MAX_LENGTH && MOBILE_APP_ID_PATTERN.test(appId);
1593
1596
  }
@@ -1646,13 +1649,22 @@ function escapeEnvValue(value) {
1646
1649
  const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
1647
1650
  return `"${escaped}"`;
1648
1651
  }
1649
- var API_PREFIX, SUPPORT_EMAIL, PLATFORM_ADMIN_EMAIL, AUDIO_CHUNK_DURATION_MS, ACTION_BATCH_INTERVAL_MS, ACTION_BATCH_MAX_SIZE, MOBILE_SCREENSHOT_INTERVAL_MS, MOBILE_ACTION_BATCH_MAX_SIZE, MOBILE_BRIDGE_PORT, MOBILE_SNAPSHOT_MAX_SIZE_BYTES, MOBILE_APP_ID_MAX_LENGTH, MOBILE_APP_ID_PATTERN, MOBILE_FOCUS_SETTLE_MS, MOBILE_POST_ACTION_SETTLE_MS, MOBILE_COLD_BOOT_SETTLE_MS, MOBILE_DEFAULT_SWIPE_DISTANCE, MOBILE_MAX_SWIPE_DISTANCE, SELECTOR_PRIORITIES, MOBILE_STEP_TYPES, BLOCKED_ENV_VARS, SPAWNED_ENV_ALLOWLIST;
1652
+ var API_PREFIX, SUPPORT_EMAIL, PLATFORM_ADMIN_EMAIL, NOREPLY_EMAIL, EMAIL_FROM_IDENTITIES, ADMIN_EMAIL_LIMITS, AUDIO_CHUNK_DURATION_MS, ACTION_BATCH_INTERVAL_MS, ACTION_BATCH_MAX_SIZE, MOBILE_SCREENSHOT_INTERVAL_MS, MOBILE_ACTION_BATCH_MAX_SIZE, MOBILE_BRIDGE_PORT, MOBILE_SNAPSHOT_MAX_SIZE_BYTES, MOBILE_APP_ID_MAX_LENGTH, MOBILE_APP_ID_PATTERN, MOBILE_FOCUS_SETTLE_MS, MOBILE_POST_ACTION_SETTLE_MS, MOBILE_COLD_BOOT_SETTLE_MS, MOBILE_DEFAULT_SWIPE_DISTANCE, MOBILE_MAX_SWIPE_DISTANCE, SELECTOR_PRIORITIES, MOBILE_STEP_TYPES, BLOCKED_ENV_VARS, SPAWNED_ENV_ALLOWLIST;
1650
1653
  var init_constants = __esm({
1651
1654
  "../shared/dist/constants.js"() {
1652
1655
  "use strict";
1653
1656
  API_PREFIX = "/api";
1654
1657
  SUPPORT_EMAIL = "support@validate.qa";
1655
1658
  PLATFORM_ADMIN_EMAIL = "admin@validate.qa";
1659
+ NOREPLY_EMAIL = "noreply@validate.qa";
1660
+ EMAIL_FROM_IDENTITIES = ["noreply", "support", "admin"];
1661
+ ADMIN_EMAIL_LIMITS = {
1662
+ recipientsPerSend: 100,
1663
+ recipientNameLength: 200,
1664
+ subjectLength: 200,
1665
+ bodyLength: 5e4,
1666
+ templateNameLength: 120
1667
+ };
1656
1668
  AUDIO_CHUNK_DURATION_MS = 3e4;
1657
1669
  ACTION_BATCH_INTERVAL_MS = 2e3;
1658
1670
  ACTION_BATCH_MAX_SIZE = 10;
@@ -1749,7 +1761,18 @@ var init_constants = __esm({
1749
1761
  "MINIMAX_API_KEY",
1750
1762
  "ANTHROPIC_API_KEY",
1751
1763
  "NVIDIA_API_KEY",
1752
- "DEEPSEEK_API_KEY"
1764
+ "DEEPSEEK_API_KEY",
1765
+ // Run-scoped callback capabilities. The parent runner injects these after
1766
+ // sanitization; customer variables must never impersonate the control plane.
1767
+ "AUTH_FILE",
1768
+ "SHARED_AUTH_INJECT",
1769
+ "VALIDATE_RUN_ID",
1770
+ "VALIDATE_CLAIM_EPOCH",
1771
+ "VALIDATE_AUTH_CONFIG_HASH",
1772
+ "VALIDATE_TASK_ARN",
1773
+ "VALIDATE_RUNNER_AUTH",
1774
+ "VALIDATE_SERVER_URL",
1775
+ "VALIDATE_LIVE_BROWSER_STATE_PATH"
1753
1776
  ]);
1754
1777
  SPAWNED_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
1755
1778
  // Runtime essentials - Node.js and Playwright need these to function
@@ -5017,12 +5040,23 @@ var init_voice_config = __esm({
5017
5040
  }
5018
5041
  });
5019
5042
 
5043
+ // ../shared/dist/feature-flags.js
5044
+ function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
5045
+ return value?.trim().toLowerCase() !== "false";
5046
+ }
5047
+ var init_feature_flags = __esm({
5048
+ "../shared/dist/feature-flags.js"() {
5049
+ "use strict";
5050
+ }
5051
+ });
5052
+
5020
5053
  // ../shared/dist/index.js
5021
5054
  var dist_exports = {};
5022
5055
  __export(dist_exports, {
5023
5056
  ACTION_BATCH_INTERVAL_MS: () => ACTION_BATCH_INTERVAL_MS,
5024
5057
  ACTION_BATCH_MAX_SIZE: () => ACTION_BATCH_MAX_SIZE,
5025
5058
  ADDON_DEFINITIONS: () => ADDON_DEFINITIONS,
5059
+ ADMIN_EMAIL_LIMITS: () => ADMIN_EMAIL_LIMITS,
5026
5060
  AI_CREDIT_MARGIN: () => AI_CREDIT_MARGIN,
5027
5061
  API_PREFIX: () => API_PREFIX,
5028
5062
  AUDIO_CHUNK_DURATION_MS: () => AUDIO_CHUNK_DURATION_MS,
@@ -5036,6 +5070,7 @@ __export(dist_exports, {
5036
5070
  DEFAULT_TTS_SPEED: () => DEFAULT_TTS_SPEED,
5037
5071
  DEFAULT_TTS_VOICE_ID: () => DEFAULT_TTS_VOICE_ID,
5038
5072
  DEFAULT_UI_VARIANT: () => DEFAULT_UI_VARIANT,
5073
+ EMAIL_FROM_IDENTITIES: () => EMAIL_FROM_IDENTITIES,
5039
5074
  ENVIRONMENTAL_ERROR_PREFIXES: () => ENVIRONMENTAL_ERROR_PREFIXES,
5040
5075
  LEGACY_TTS_VOICE_ID_MAP: () => LEGACY_TTS_VOICE_ID_MAP,
5041
5076
  MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
@@ -5054,6 +5089,7 @@ __export(dist_exports, {
5054
5089
  MOBILE_SCREENSHOT_INTERVAL_MS: () => MOBILE_SCREENSHOT_INTERVAL_MS,
5055
5090
  MOBILE_SNAPSHOT_MAX_SIZE_BYTES: () => MOBILE_SNAPSHOT_MAX_SIZE_BYTES,
5056
5091
  MOBILE_STEP_TYPES: () => MOBILE_STEP_TYPES,
5092
+ NOREPLY_EMAIL: () => NOREPLY_EMAIL,
5057
5093
  PLAN_DEFINITIONS: () => PLAN_DEFINITIONS,
5058
5094
  PLAN_ORDER: () => PLAN_ORDER,
5059
5095
  PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
@@ -5130,12 +5166,14 @@ __export(dist_exports, {
5130
5166
  isCreditPackAmountCents: () => isCreditPackAmountCents,
5131
5167
  isDbPricingPlan: () => isDbPricingPlan,
5132
5168
  isEditableMobileClassName: () => isEditableMobileClassName,
5169
+ isEmailFromIdentity: () => isEmailFromIdentity,
5133
5170
  isEnvironmentalError: () => isEnvironmentalError,
5134
5171
  isMobilePlatform: () => isMobilePlatform,
5135
5172
  isPricingPlan: () => isPricingPlan,
5136
5173
  isRunnableStep: () => isRunnableStep,
5137
5174
  isSafeDeepLink: () => isSafeDeepLink,
5138
5175
  isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
5176
+ isSharedAuthInjectEnabled: () => isSharedAuthInjectEnabled,
5139
5177
  isSttModelForProvider: () => isSttModelForProvider,
5140
5178
  isTestReadyForPlatform: () => isTestReadyForPlatform,
5141
5179
  isTrialExpired: () => isTrialExpired,
@@ -5202,6 +5240,7 @@ var init_dist = __esm({
5202
5240
  init_test_counting();
5203
5241
  init_origin_domain();
5204
5242
  init_voice_config();
5243
+ init_feature_flags();
5205
5244
  }
5206
5245
  });
5207
5246
 
@@ -10665,7 +10704,7 @@ var require_transport = __commonJS({
10665
10704
  "../../node_modules/pino/lib/transport.js"(exports, module) {
10666
10705
  "use strict";
10667
10706
  var { createRequire } = __require("module");
10668
- var { existsSync: existsSync4 } = __require("fs");
10707
+ var { existsSync: existsSync3 } = __require("fs");
10669
10708
  var getCallers = require_caller();
10670
10709
  var { join: join7, isAbsolute, sep } = __require("path");
10671
10710
  var { fileURLToPath } = __require("url");
@@ -10739,7 +10778,7 @@ var require_transport = __commonJS({
10739
10778
  return false;
10740
10779
  }
10741
10780
  }
10742
- return isAbsolute(path) && !existsSync4(path);
10781
+ return isAbsolute(path) && !existsSync3(path);
10743
10782
  }
10744
10783
  function stripQuotes(value) {
10745
10784
  const first = value[0];
@@ -76154,6 +76193,126 @@ export const test = base.extend({
76154
76193
  }
76155
76194
  });
76156
76195
 
76196
+ // ../runner-core/dist/services/run-callback-proxy.js
76197
+ var require_run_callback_proxy = __commonJS({
76198
+ "../runner-core/dist/services/run-callback-proxy.js"(exports) {
76199
+ "use strict";
76200
+ Object.defineProperty(exports, "__esModule", { value: true });
76201
+ exports.startRunCallbackProxy = startRunCallbackProxy;
76202
+ var node_crypto_1 = __require("crypto");
76203
+ var node_http_1 = __require("http");
76204
+ var MAX_CALLBACK_BODY_BYTES = 5 * 1024 * 1024;
76205
+ var CALLBACK_TIMEOUT_MS = 3e4;
76206
+ function sendJson(res, status, body) {
76207
+ const payload = JSON.stringify(body);
76208
+ res.writeHead(status, {
76209
+ "cache-control": "no-store",
76210
+ "content-type": "application/json",
76211
+ "content-length": Buffer.byteLength(payload)
76212
+ });
76213
+ res.end(payload);
76214
+ }
76215
+ async function readRequestBody(req) {
76216
+ const chunks = [];
76217
+ let total = 0;
76218
+ for await (const chunk of req) {
76219
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
76220
+ total += buffer.length;
76221
+ if (total > MAX_CALLBACK_BODY_BYTES)
76222
+ throw new Error("callback body too large");
76223
+ chunks.push(buffer);
76224
+ }
76225
+ return Buffer.concat(chunks);
76226
+ }
76227
+ async function startRunCallbackProxy(input) {
76228
+ const upstream = new URL(input.serverUrl);
76229
+ if (upstream.protocol !== "http:" && upstream.protocol !== "https:") {
76230
+ throw new Error("callback server URL must use http or https");
76231
+ }
76232
+ const runPath = `/api/runner/runs/${encodeURIComponent(input.runId)}`;
76233
+ const allowedPaths = /* @__PURE__ */ new Set([`${runPath}/credential`, `${runPath}/auth-state`]);
76234
+ const token = (0, node_crypto_1.randomBytes)(32).toString("base64url");
76235
+ const authHeader = `Bearer ${token}`;
76236
+ const server3 = (0, node_http_1.createServer)(async (req, res) => {
76237
+ try {
76238
+ const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
76239
+ if (req.method !== "POST" || !allowedPaths.has(requestUrl.pathname) || requestUrl.search) {
76240
+ sendJson(res, 404, { error: "Callback route not available" });
76241
+ return;
76242
+ }
76243
+ if (req.headers.authorization !== authHeader) {
76244
+ sendJson(res, 401, { error: "Invalid callback capability" });
76245
+ return;
76246
+ }
76247
+ const body = await readRequestBody(req);
76248
+ let parsedBody;
76249
+ try {
76250
+ const parsed = JSON.parse(body.toString("utf8"));
76251
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
76252
+ throw new Error("invalid body");
76253
+ parsedBody = parsed;
76254
+ } catch {
76255
+ sendJson(res, 400, { error: "Callback body must be a JSON object" });
76256
+ return;
76257
+ }
76258
+ const upstreamBody = JSON.stringify({
76259
+ ...parsedBody,
76260
+ ...input.taskArn ? { taskArn: input.taskArn } : {},
76261
+ ...input.claimEpoch !== void 0 ? { claimEpoch: input.claimEpoch } : {},
76262
+ ...input.authConfigHash ? { authConfigHash: input.authConfigHash } : {}
76263
+ });
76264
+ const destination = new URL(requestUrl.pathname, upstream);
76265
+ const response = await fetch(destination, {
76266
+ method: "POST",
76267
+ headers: {
76268
+ Authorization: input.upstreamAuthHeader,
76269
+ "Content-Type": "application/json"
76270
+ },
76271
+ body: upstreamBody,
76272
+ redirect: "error",
76273
+ signal: AbortSignal.timeout(CALLBACK_TIMEOUT_MS)
76274
+ });
76275
+ const responseBody = Buffer.from(await response.arrayBuffer());
76276
+ res.writeHead(response.status, {
76277
+ "cache-control": "no-store",
76278
+ "content-type": response.headers.get("content-type") ?? "application/json",
76279
+ "content-length": responseBody.length
76280
+ });
76281
+ res.end(responseBody);
76282
+ } catch (error2) {
76283
+ const message = error2 instanceof Error && error2.message === "callback body too large" ? error2.message : "Callback forwarding failed";
76284
+ sendJson(res, message === "callback body too large" ? 413 : 502, { error: message });
76285
+ }
76286
+ });
76287
+ server3.on("clientError", (_error, socket) => socket.destroy());
76288
+ await new Promise((resolve2, reject) => {
76289
+ const onError = (error2) => reject(error2);
76290
+ server3.once("error", onError);
76291
+ server3.listen(0, "127.0.0.1", () => {
76292
+ server3.off("error", onError);
76293
+ resolve2();
76294
+ });
76295
+ });
76296
+ const address = server3.address();
76297
+ if (!address) {
76298
+ server3.close();
76299
+ throw new Error("callback proxy failed to bind");
76300
+ }
76301
+ let closed = false;
76302
+ return {
76303
+ serverUrl: `http://127.0.0.1:${address.port}`,
76304
+ authHeader,
76305
+ close: async () => {
76306
+ if (closed)
76307
+ return;
76308
+ closed = true;
76309
+ await new Promise((resolve2) => server3.close(() => resolve2()));
76310
+ }
76311
+ };
76312
+ }
76313
+ }
76314
+ });
76315
+
76157
76316
  // ../runner-core/dist/services/playwright-native.js
76158
76317
  var require_playwright_native = __commonJS({
76159
76318
  "../runner-core/dist/services/playwright-native.js"(exports) {
@@ -76198,6 +76357,7 @@ var require_playwright_native = __commonJS({
76198
76357
  Object.defineProperty(exports, "__esModule", { value: true });
76199
76358
  exports.scrubHarRequestBody = exports.buildOriginScopedApiFixtures = exports.isAppOriginRequest = exports.resolveApiAuthHeaders = exports.parseApiAuthHeaders = void 0;
76200
76359
  exports.executeTestViaNativePlaywright = executeTestViaNativePlaywright2;
76360
+ exports.buildNativePlaywrightChildEnv = buildNativePlaywrightChildEnv;
76201
76361
  exports.parseJsonResults = parseJsonResults2;
76202
76362
  exports.parseHarToApiSummary = parseHarToApiSummary;
76203
76363
  var promises_1 = __require("fs/promises");
@@ -76209,6 +76369,7 @@ var require_playwright_native = __commonJS({
76209
76369
  var api_call_redaction_js_1 = require_api_call_redaction();
76210
76370
  var har_stream_js_1 = require_har_stream();
76211
76371
  var api_auth_headers_js_1 = require_api_auth_headers();
76372
+ var run_callback_proxy_js_1 = require_run_callback_proxy();
76212
76373
  var api_auth_headers_js_2 = require_api_auth_headers();
76213
76374
  Object.defineProperty(exports, "parseApiAuthHeaders", { enumerable: true, get: function() {
76214
76375
  return api_auth_headers_js_2.parseApiAuthHeaders;
@@ -76231,7 +76392,7 @@ var require_playwright_native = __commonJS({
76231
76392
  var RUNS_DIR = process.env.RUNS_DIR || (0, path_1.join)(PACKAGE_DIR, ".qa-runs");
76232
76393
  var PW_CLI = __require.resolve("@playwright/test/cli");
76233
76394
  async function executeTestViaNativePlaywright2(options) {
76234
- const { playwrightCode, baseUrl, testName, testVariables, storageStatePath, extraHttpHeaders, validateContext, liveBrowserHandle, onLiveBrowserSnapshot } = options;
76395
+ const { playwrightCode, baseUrl, testName, testVariables, authStateCapturePath, storageStatePath, extraHttpHeaders, validateContext, liveBrowserHandle, onLiveBrowserSnapshot } = options;
76235
76396
  const logs2 = [];
76236
76397
  const screenshots = [];
76237
76398
  logs2.push(`\u{1F3AD} Native Playwright starting`);
@@ -76241,6 +76402,7 @@ var require_playwright_native = __commonJS({
76241
76402
  logs2.push("\u26A0\uFE0F WARNING: baseUrl is empty - test will likely fail with navigation errors");
76242
76403
  }
76243
76404
  let runDir = null;
76405
+ let callbackProxy;
76244
76406
  try {
76245
76407
  runDir = (0, path_1.join)(RUNS_DIR, (0, crypto_1.randomUUID)());
76246
76408
  await (0, promises_1.mkdir)(runDir, { recursive: true });
@@ -76500,35 +76662,51 @@ module.exports = {
76500
76662
  const configPath = (0, path_1.join)(runDir, "playwright.config.js");
76501
76663
  await (0, promises_1.writeFile)(configPath, config);
76502
76664
  const isolation = await (0, chromium_isolation_js_1.createChromiumIsolation)(runDir);
76503
- const envWithIsolation = {
76504
- ...testVariables ?? {},
76665
+ const processClaimEpoch = Number(process.env.CLAIM_EPOCH);
76666
+ const callbackClaimEpoch = Number.isSafeInteger(validateContext?.claimEpoch) ? validateContext?.claimEpoch : Number.isSafeInteger(processClaimEpoch) && processClaimEpoch >= 0 ? processClaimEpoch : void 0;
76667
+ if (validateContext?.runId && validateContext.serverUrl && validateContext.authHeader) {
76668
+ try {
76669
+ callbackProxy = await (0, run_callback_proxy_js_1.startRunCallbackProxy)({
76670
+ runId: validateContext.runId,
76671
+ serverUrl: validateContext.serverUrl,
76672
+ upstreamAuthHeader: validateContext.authHeader,
76673
+ taskArn: process.env.VALIDATE_TASK_ARN,
76674
+ claimEpoch: callbackClaimEpoch,
76675
+ authConfigHash: process.env.VALIDATE_AUTH_CONFIG_HASH
76676
+ });
76677
+ } catch (error3) {
76678
+ logs2.push(`Callback proxy unavailable: ${error3 instanceof Error ? error3.message : String(error3)}`);
76679
+ }
76680
+ }
76681
+ const childTestVariables = { ...testVariables ?? {} };
76682
+ const trustedRuntimeEnv = {
76505
76683
  ...isolation.env,
76506
- // persistCredential() callback envs — set only when the runner supplied
76507
- // validateContext. Without these the helper inside the test no-ops with
76508
- // a warning, which is the correct behavior when the test is being run
76509
- // manually (outside the validate.qa harness).
76510
- ...validateContext?.runId ? { VALIDATE_RUN_ID: validateContext.runId } : {},
76511
- ...validateContext?.serverUrl ? { VALIDATE_SERVER_URL: validateContext.serverUrl } : {},
76512
- ...validateContext?.authHeader ? { VALIDATE_RUNNER_AUTH: validateContext.authHeader } : {},
76684
+ // Generated callbacks receive only a random loopback capability. The global
76685
+ // runner bearer token and real control-plane URL stay in the parent process.
76686
+ ...callbackProxy && validateContext?.runId ? {
76687
+ VALIDATE_RUN_ID: validateContext.runId,
76688
+ ...callbackClaimEpoch !== void 0 ? { VALIDATE_CLAIM_EPOCH: String(callbackClaimEpoch) } : {},
76689
+ VALIDATE_SERVER_URL: callbackProxy.serverUrl,
76690
+ VALIDATE_RUNNER_AUTH: callbackProxy.authHeader
76691
+ } : {},
76513
76692
  // Owning Fargate taskArn (a per-task constant set once at task boot in
76514
76693
  // execute-one.ts; absent on the local/Railway runner). Read from
76515
76694
  // process.env rather than validateContext since it's process-wide, not
76516
76695
  // per-run. Lets persistCredential() prove ownership to /credential.
76517
76696
  ...process.env.VALIDATE_TASK_ARN ? { VALIDATE_TASK_ARN: process.env.VALIDATE_TASK_ARN } : {},
76697
+ ...authStateCapturePath ? { AUTH_FILE: authStateCapturePath } : {},
76518
76698
  ...liveBrowserStatePath ? { VALIDATE_LIVE_BROWSER_STATE_PATH: liveBrowserStatePath } : {},
76519
- // Shared-auth kill-switch (SHARED_AUTH_INJECT) must reach the child: the stamped
76520
- // __vqaLogin's early-publish POST is gated on it at RUNTIME. buildSandboxedEnv only
76521
- // copies an allowlist from process.env (SHARED_AUTH_INJECT is not on it), so without
76522
- // this forward the flag reads as undefined in the test and the early publish is dead
76523
- // code. Forwarded (not allowlisted) so it stays a per-run toggle; absent → inert.
76699
+ // Preserve an explicit shared-auth kill-switch in the child. Unset defaults on in
76700
+ // both parent and generated __vqaLogin code; "false" must still cross this process
76701
+ // boundary to disable the early publish POST. It remains a per-run toggle.
76524
76702
  ...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {}
76525
76703
  };
76526
- delete envWithIsolation.API_AUTH_HEADERS;
76704
+ delete childTestVariables.API_AUTH_HEADERS;
76527
76705
  logs2.push("\u{1F680} Spawning playwright test...");
76528
76706
  const stopLiveBrowserPolling = liveBrowserStatePath && liveBrowserHandle ? startLiveBrowserStatePolling(liveBrowserStatePath, liveBrowserHandle, onLiveBrowserSnapshot) : null;
76529
76707
  let playResult;
76530
76708
  try {
76531
- playResult = await runPlaywright(configPath, envWithIsolation);
76709
+ playResult = await runPlaywright(configPath, childTestVariables, trustedRuntimeEnv);
76532
76710
  } finally {
76533
76711
  await stopLiveBrowserPolling?.();
76534
76712
  }
@@ -76612,6 +76790,8 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
76612
76790
  error: msg
76613
76791
  };
76614
76792
  } finally {
76793
+ await callbackProxy?.close().catch(() => {
76794
+ });
76615
76795
  if (runDir) {
76616
76796
  await (0, promises_1.rm)(runDir, { recursive: true, force: true }).catch(() => {
76617
76797
  });
@@ -76663,9 +76843,9 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
76663
76843
  stopped = true;
76664
76844
  };
76665
76845
  }
76666
- function runPlaywright(configPath, extraEnv) {
76846
+ function runPlaywright(configPath, testVariables, trustedRuntimeEnv) {
76667
76847
  return new Promise((resolvePromise) => {
76668
- const sandboxedEnv = (0, shared_1.buildSandboxedEnv)(process.env, extraEnv);
76848
+ const sandboxedEnv = buildNativePlaywrightChildEnv(process.env, testVariables, trustedRuntimeEnv);
76669
76849
  const proc = (0, spawn_with_tree_kill_js_1.spawnDetachedGroup)(process.execPath, [PW_CLI, "test", "--config", configPath], {
76670
76850
  cwd: PACKAGE_DIR,
76671
76851
  env: sandboxedEnv,
@@ -76708,6 +76888,12 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
76708
76888
  });
76709
76889
  });
76710
76890
  }
76891
+ function buildNativePlaywrightChildEnv(processEnv, testVariables, trustedRuntimeEnv) {
76892
+ return {
76893
+ ...(0, shared_1.buildSandboxedEnv)(processEnv, testVariables),
76894
+ ...trustedRuntimeEnv ?? {}
76895
+ };
76896
+ }
76711
76897
  async function collectScreenshots(dir, screenshots) {
76712
76898
  let entries;
76713
76899
  try {
@@ -80429,10 +80615,12 @@ var require_auth_recipe = __commonJS({
80429
80615
  exports.upsertAuthBlock = upsertAuthBlock;
80430
80616
  exports.buildHealAuthStamp = buildHealAuthStamp;
80431
80617
  var crypto_1 = __require("crypto");
80618
+ var shared_1 = (init_dist(), __toCommonJS(dist_exports));
80432
80619
  var discovery_lint_flags_js_1 = require_discovery_lint_flags();
80433
- exports.AUTH_MARKER_VERSION = "v1";
80620
+ exports.AUTH_MARKER_VERSION = "v3";
80434
80621
  var START_MARKER = `// >>> VALIDATE_QA_AUTH ${exports.AUTH_MARKER_VERSION} \u2014 generated, do not edit >>>`;
80435
80622
  var END_MARKER = `// <<< VALIDATE_QA_AUTH ${exports.AUTH_MARKER_VERSION} <<<`;
80623
+ var SHARED_AUTH_RUNTIME_ENABLED = `!/^false$/i.test((process.env.SHARED_AUTH_INJECT ?? '').trim())`;
80436
80624
  var AUTH_REGION_RE = /\/\/ >>> VALIDATE_QA_AUTH[^\n]*\r?\n[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH[^\n]*\r?\n?/g;
80437
80625
  function computeRecipeHash(input) {
80438
80626
  const canonical = JSON.stringify({
@@ -80510,16 +80698,18 @@ var require_auth_recipe = __commonJS({
80510
80698
  if (opts?.warmGuard) {
80511
80699
  const warmLanding = recipe.redirectRoute ?? (recipe.authProof.kind === "url" ? recipe.authProof.route : recipe.preNavRoute);
80512
80700
  const loginRoute = recipe.route ?? recipe.preNavRoute;
80513
- lines.push(` await page.goto(${JSON.stringify(warmLanding)}, { waitUntil: 'domcontentloaded' }).catch(() => {});`);
80701
+ lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED}) {`);
80702
+ lines.push(` await page.goto(${JSON.stringify(warmLanding)}, { waitUntil: 'domcontentloaded' }).catch(() => {});`);
80514
80703
  if (recipe.authProof.kind === "url") {
80515
80704
  const pred = urlProofPredicateExpr(recipe.authProof.route);
80516
- lines.push(` await page.waitForURL((u) => !(${pred})(u), { timeout: 2000 }).catch(() => {});`);
80517
- lines.push(` if ((${pred})(new URL(page.url())) && !(await page.locator('input[type="password"]').first().isVisible().catch(() => false))) return;`);
80705
+ lines.push(` await page.waitForURL((u) => !(${pred})(u), { timeout: 2000 }).catch(() => {});`);
80706
+ lines.push(` if ((${pred})(new URL(page.url())) && !(await page.locator('input[type="password"]').first().isVisible().catch(() => false))) return;`);
80518
80707
  } else if (loginRoute) {
80519
- lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false) && new URL(page.url()).pathname !== new URL(${JSON.stringify(loginRoute)}, page.url()).pathname) return;`);
80708
+ lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false) && new URL(page.url()).pathname !== new URL(${JSON.stringify(loginRoute)}, page.url()).pathname) return;`);
80520
80709
  } else {
80521
- lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false)) return;`);
80710
+ lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false)) return;`);
80522
80711
  }
80712
+ lines.push(` }`);
80523
80713
  }
80524
80714
  lines.push(` await page.goto(${JSON.stringify(recipe.preNavRoute)}, { waitUntil: 'domcontentloaded' });`);
80525
80715
  for (const cmd of recipe.preNavCmds)
@@ -80532,11 +80722,11 @@ var require_auth_recipe = __commonJS({
80532
80722
  lines.push(renderAuthUrlWaitLine(recipe.redirectRoute));
80533
80723
  lines.push(` ${renderAuthProofAssertion(recipe.authProof)}`);
80534
80724
  if (opts?.warmGuard) {
80535
- lines.push(` if (process.env.AUTH_FILE) await page.context().storageState({ path: process.env.AUTH_FILE });`);
80536
- lines.push(` if (process.env.SHARED_AUTH_INJECT === 'true' && process.env.VALIDATE_TASK_ARN && process.env.VALIDATE_SERVER_URL && process.env.VALIDATE_RUN_ID) {`);
80725
+ lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED} && process.env.AUTH_FILE) await page.context().storageState({ path: process.env.AUTH_FILE });`);
80726
+ lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED} && process.env.VALIDATE_TASK_ARN && process.env.VALIDATE_CLAIM_EPOCH && process.env.VALIDATE_SERVER_URL && process.env.VALIDATE_RUN_ID) {`);
80537
80727
  lines.push(` try {`);
80538
80728
  lines.push(` const __vqaState = await page.context().storageState();`);
80539
- lines.push(` await page.request.post(process.env.VALIDATE_SERVER_URL + '/api/runner/runs/' + process.env.VALIDATE_RUN_ID + '/auth-state', { headers: { Authorization: process.env.VALIDATE_RUNNER_AUTH || '' }, data: { storageState: JSON.stringify(__vqaState), taskArn: process.env.VALIDATE_TASK_ARN }, timeout: 10000 });`);
80729
+ lines.push(` await page.request.post(process.env.VALIDATE_SERVER_URL + '/api/runner/runs/' + process.env.VALIDATE_RUN_ID + '/auth-state', { headers: { Authorization: process.env.VALIDATE_RUNNER_AUTH || '' }, data: { storageState: JSON.stringify(__vqaState), taskArn: process.env.VALIDATE_TASK_ARN, claimEpoch: Number(process.env.VALIDATE_CLAIM_EPOCH) }, timeout: 10000 });`);
80540
80730
  lines.push(` } catch {}`);
80541
80731
  lines.push(` }`);
80542
80732
  }
@@ -80548,6 +80738,8 @@ var require_auth_recipe = __commonJS({
80548
80738
  "AUTH_FILE",
80549
80739
  "SHARED_AUTH_INJECT",
80550
80740
  "VALIDATE_RUN_ID",
80741
+ "VALIDATE_CLAIM_EPOCH",
80742
+ "VALIDATE_AUTH_CONFIG_HASH",
80551
80743
  "VALIDATE_TASK_ARN",
80552
80744
  "VALIDATE_RUNNER_AUTH",
80553
80745
  "VALIDATE_SERVER_URL"
@@ -80720,7 +80912,7 @@ ${tail}`;
80720
80912
  let result = stripAuthRegions(code);
80721
80913
  result = result.replace(/^[ \t]*await[ \t]+__vqaLogin[ \t]*\([ \t]*page[ \t]*\)[ \t]*;?[ \t]*\r?\n/gm, "");
80722
80914
  result = ensureExpectImported(result);
80723
- const warmGuard = process.env.SHARED_AUTH_INJECT === "true";
80915
+ const warmGuard = (0, shared_1.isSharedAuthInjectEnabled)();
80724
80916
  result = insertAfterPwImport(result, renderAuthFn(ctx.recipe, { warmGuard }));
80725
80917
  result = ensureLoginCall(result);
80726
80918
  const n = countAuthRegions(result);
@@ -85850,9 +86042,9 @@ ${testOpen}`;
85850
86042
  basePath: options?.basePath
85851
86043
  });
85852
86044
  const leakedSensitiveKeys = findSensitiveLiteralLeaks(playwrightCode, options?.sensitiveEnvMap);
85853
- const authGuardStamped = !!options?.authRecipe && rawTags.includes("@needs-auth") && !rawTags.includes("@auth") && process.env.SHARED_AUTH_INJECT === "true";
85854
- const baseTags = ensureDiscoveredTag(Array.isArray(test.tags) ? test.tags : ["@discovered", "@regression"]);
85855
- const tags = authGuardStamped && !baseTags.includes("@auth-guarded") ? [...baseTags, "@auth-guarded"] : baseTags;
86045
+ const authGuardStamped = !!options?.authRecipe && rawTags.includes("@needs-auth") && !rawTags.includes("@auth") && (0, shared_1.isSharedAuthInjectEnabled)() && /\/\/ >>> VALIDATE_QA_AUTH v\d+\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\d+\b/.test(playwrightCode);
86046
+ const baseTags = ensureDiscoveredTag(Array.isArray(test.tags) ? test.tags : ["@discovered", "@regression"]).filter((tag) => tag !== "@auth-guarded");
86047
+ const tags = authGuardStamped ? [...baseTags, "@auth-guarded"] : baseTags;
85856
86048
  return {
85857
86049
  name: String(test.name || "Unnamed test").slice(0, 200),
85858
86050
  description: String(test.description || test.name || "").slice(0, 500),
@@ -99667,7 +99859,7 @@ ${lanes.join("\n")}
99667
99859
  return process.memoryUsage().heapUsed;
99668
99860
  },
99669
99861
  getFileSize(path) {
99670
- const stat = statSync2(path);
99862
+ const stat = statSync(path);
99671
99863
  if (stat == null ? void 0 : stat.isFile()) {
99672
99864
  return stat.size;
99673
99865
  }
@@ -99711,7 +99903,7 @@ ${lanes.join("\n")}
99711
99903
  }
99712
99904
  };
99713
99905
  return nodeSystem;
99714
- function statSync2(path) {
99906
+ function statSync(path) {
99715
99907
  try {
99716
99908
  return _fs.statSync(path, statSyncOptions);
99717
99909
  } catch {
@@ -99770,7 +99962,7 @@ ${lanes.join("\n")}
99770
99962
  activeSession.post("Profiler.stop", (err, { profile }) => {
99771
99963
  var _a;
99772
99964
  if (!err) {
99773
- if ((_a = statSync2(profilePath)) == null ? void 0 : _a.isDirectory()) {
99965
+ if ((_a = statSync(profilePath)) == null ? void 0 : _a.isDirectory()) {
99774
99966
  profilePath = _path.join(profilePath, `${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`);
99775
99967
  }
99776
99968
  try {
@@ -99890,7 +100082,7 @@ ${lanes.join("\n")}
99890
100082
  let stat;
99891
100083
  if (typeof dirent === "string" || dirent.isSymbolicLink()) {
99892
100084
  const name = combinePaths(path, entry);
99893
- stat = statSync2(name);
100085
+ stat = statSync(name);
99894
100086
  if (!stat) {
99895
100087
  continue;
99896
100088
  }
@@ -99914,7 +100106,7 @@ ${lanes.join("\n")}
99914
100106
  return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames2, process.cwd(), depth, getAccessibleFileSystemEntries, realpath);
99915
100107
  }
99916
100108
  function fileSystemEntryExists(path, entryKind) {
99917
- const stat = statSync2(path);
100109
+ const stat = statSync(path);
99918
100110
  if (!stat) {
99919
100111
  return false;
99920
100112
  }
@@ -99956,7 +100148,7 @@ ${lanes.join("\n")}
99956
100148
  }
99957
100149
  function getModifiedTime3(path) {
99958
100150
  var _a;
99959
- return (_a = statSync2(path)) == null ? void 0 : _a.mtime;
100151
+ return (_a = statSync(path)) == null ? void 0 : _a.mtime;
99960
100152
  }
99961
100153
  function setModifiedTime(path, time) {
99962
100154
  try {
@@ -308467,6 +308659,7 @@ var require_regression_runner = __commonJS({
308467
308659
  var chromium_isolation_js_1 = require_chromium_isolation();
308468
308660
  var browser_config_js_1 = require_browser_config();
308469
308661
  var spawn_with_tree_kill_js_1 = require_spawn_with_tree_kill();
308662
+ var run_callback_proxy_js_1 = require_run_callback_proxy();
308470
308663
  var SUITE_SPAWN_TIMEOUT_MS = 85 * 60 * 1e3;
308471
308664
  var PACKAGE_DIR = (0, path_1.resolve)(__dirname, "..", "..");
308472
308665
  var RUNS_DIR = process.env.RUNS_DIR || (0, path_1.join)(PACKAGE_DIR, ".qa-runs");
@@ -308481,6 +308674,7 @@ var require_regression_runner = __commonJS({
308481
308674
  const tarPath = (0, path_1.join)(RUNS_DIR, `regression-${runId}.tar.gz`);
308482
308675
  const startTime = Date.now();
308483
308676
  let logs2 = "";
308677
+ let callbackProxy;
308484
308678
  try {
308485
308679
  await (0, promises_1.mkdir)(extractDir, { recursive: true });
308486
308680
  const tarballUrl = `https://api.github.com/repos/${repoOwner}/${repoName}/tarball/${repoBranch}`;
@@ -308510,19 +308704,40 @@ var require_regression_runner = __commonJS({
308510
308704
  await (0, promises_1.writeFile)((0, path_1.join)(extractDir, ".env"), envContent, "utf-8");
308511
308705
  const jsonResultsPath = (0, path_1.join)(extractDir, "test-results", "results.json");
308512
308706
  const isolation = await (0, chromium_isolation_js_1.createChromiumIsolation)(extractDir);
308707
+ const processClaimEpoch = Number(process.env.CLAIM_EPOCH);
308708
+ const callbackClaimEpoch = Number.isSafeInteger(opts.claimEpoch) ? opts.claimEpoch : Number.isSafeInteger(processClaimEpoch) && processClaimEpoch >= 0 ? processClaimEpoch : void 0;
308709
+ if (opts.validateServerUrl && opts.validateRunnerAuthHeader) {
308710
+ try {
308711
+ callbackProxy = await (0, run_callback_proxy_js_1.startRunCallbackProxy)({
308712
+ runId,
308713
+ serverUrl: opts.validateServerUrl,
308714
+ upstreamAuthHeader: opts.validateRunnerAuthHeader,
308715
+ taskArn: process.env.VALIDATE_TASK_ARN,
308716
+ claimEpoch: callbackClaimEpoch,
308717
+ authConfigHash: process.env.VALIDATE_AUTH_CONFIG_HASH
308718
+ });
308719
+ } catch (error2) {
308720
+ logs2 += `Callback proxy unavailable: ${error2 instanceof Error ? error2.message : String(error2)}
308721
+ `;
308722
+ }
308723
+ }
308513
308724
  const runEnv = {
308514
308725
  ...(0, shared_1.buildSandboxedEnv)(process.env, testVariables),
308515
308726
  ...isolation.env,
308516
308727
  CI: "true",
308517
308728
  PLAYWRIGHT_JSON_OUTPUT_NAME: jsonResultsPath,
308518
- // Callback envs for tests/setup/credentials.ts → persistCredential.
308519
- // Tests that rotate a credential POST back to the server using these.
308520
- VALIDATE_RUN_ID: runId,
308521
- ...opts.validateServerUrl ? { VALIDATE_SERVER_URL: opts.validateServerUrl } : {},
308522
- ...opts.validateRunnerAuthHeader ? { VALIDATE_RUNNER_AUTH: opts.validateRunnerAuthHeader } : {},
308729
+ // The suite receives a random run-scoped loopback callback, never the
308730
+ // global runner bearer credential.
308731
+ ...callbackProxy ? {
308732
+ VALIDATE_RUN_ID: runId,
308733
+ ...callbackClaimEpoch !== void 0 ? { VALIDATE_CLAIM_EPOCH: String(callbackClaimEpoch) } : {},
308734
+ VALIDATE_SERVER_URL: callbackProxy.serverUrl,
308735
+ VALIDATE_RUNNER_AUTH: callbackProxy.authHeader
308736
+ } : {},
308523
308737
  // Owning Fargate taskArn (per-task constant; unset off-Fargate). Forwarded
308524
308738
  // so persistCredential() can prove ownership to the /credential fence.
308525
- ...process.env.VALIDATE_TASK_ARN ? { VALIDATE_TASK_ARN: process.env.VALIDATE_TASK_ARN } : {}
308739
+ ...process.env.VALIDATE_TASK_ARN ? { VALIDATE_TASK_ARN: process.env.VALIDATE_TASK_ARN } : {},
308740
+ ...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {}
308526
308741
  };
308527
308742
  logs2 += `Running playwright --project=${opts.suiteProject ?? "suite"}...
308528
308743
  `;
@@ -308551,6 +308766,8 @@ var require_regression_runner = __commonJS({
308551
308766
  error: String(err)
308552
308767
  };
308553
308768
  } finally {
308769
+ await callbackProxy?.close().catch(() => {
308770
+ });
308554
308771
  (0, promises_1.rm)(extractDir, { recursive: true, force: true }).catch(() => {
308555
308772
  });
308556
308773
  await (0, browser_config_js_1.cleanupPlaywrightTempArtifacts)().catch(() => {
@@ -324794,6 +325011,455 @@ var require_resource_governor = __commonJS({
324794
325011
  }
324795
325012
  });
324796
325013
 
325014
+ // ../runner-core/dist/services/local-auth-state.js
325015
+ var require_local_auth_state = __commonJS({
325016
+ "../runner-core/dist/services/local-auth-state.js"(exports) {
325017
+ "use strict";
325018
+ Object.defineProperty(exports, "__esModule", { value: true });
325019
+ exports.LOCAL_AUTH_STATE_LOCK_ABSOLUTE_STALE_MS = exports.LOCAL_AUTH_STATE_LOCK_STALE_MS = exports.LOCAL_AUTH_STATE_CLEANUP_INTERVAL_MS = exports.LOCAL_AUTH_STATE_WAIT_MS = exports.LOCAL_AUTH_STATE_MAX_AGE_MS = void 0;
325020
+ exports.shouldSkipWarmAuthSetup = shouldSkipWarmAuthSetup2;
325021
+ exports.shouldSkipDisabledAutomaticAuthSetup = shouldSkipDisabledAutomaticAuthSetup2;
325022
+ exports.localAuthStateFingerprint = localAuthStateFingerprint;
325023
+ exports.ensurePrivateDirectory = ensurePrivateDirectory2;
325024
+ exports.isValidStorageState = isValidStorageState;
325025
+ exports.isValidStorageStateJson = isValidStorageStateJson;
325026
+ exports.isValidLocalAuthStateFile = isValidLocalAuthStateFile;
325027
+ exports.cleanupLocalAuthStateFiles = cleanupLocalAuthStateFiles;
325028
+ exports.cleanupExpiredLocalAuthStateFiles = cleanupExpiredLocalAuthStateFiles2;
325029
+ exports.localAuthStatePaths = localAuthStatePaths2;
325030
+ exports.getValidLocalAuthState = getValidLocalAuthState2;
325031
+ exports.waitForValidLocalAuthState = waitForValidLocalAuthState;
325032
+ exports.waitForLocalAuthStateProducer = waitForLocalAuthStateProducer2;
325033
+ exports.promoteLocalAuthStateCapture = promoteLocalAuthStateCapture2;
325034
+ exports.discardLocalAuthStateCapture = discardLocalAuthStateCapture2;
325035
+ exports.acquireLocalAuthStateLock = acquireLocalAuthStateLock2;
325036
+ exports.localAuthStateFilename = localAuthStateFilename;
325037
+ var node_fs_1 = __require("fs");
325038
+ var node_crypto_1 = __require("crypto");
325039
+ var node_path_1 = __require("path");
325040
+ var auth_recipe_js_1 = require_auth_recipe();
325041
+ exports.LOCAL_AUTH_STATE_MAX_AGE_MS = 60 * 60 * 1e3;
325042
+ exports.LOCAL_AUTH_STATE_WAIT_MS = 3 * 60 * 1e3;
325043
+ exports.LOCAL_AUTH_STATE_CLEANUP_INTERVAL_MS = 15 * 60 * 1e3;
325044
+ exports.LOCAL_AUTH_STATE_LOCK_STALE_MS = 2 * 60 * 1e3;
325045
+ exports.LOCAL_AUTH_STATE_LOCK_ABSOLUTE_STALE_MS = 30 * 60 * 1e3;
325046
+ function shouldSkipWarmAuthSetup2(authForceRun, sharedAuthEnabled, cacheWarm) {
325047
+ return sharedAuthEnabled && authForceRun !== true && cacheWarm;
325048
+ }
325049
+ function shouldSkipDisabledAutomaticAuthSetup2(authForceRun, authVerify, sharedAuthEnabled) {
325050
+ return !sharedAuthEnabled && authForceRun !== true && authVerify !== true;
325051
+ }
325052
+ function normalizeOrigin(value) {
325053
+ try {
325054
+ return new URL(value).origin.toLowerCase();
325055
+ } catch {
325056
+ return value.trim().toLowerCase();
325057
+ }
325058
+ }
325059
+ function normalizeAppUrl(value) {
325060
+ try {
325061
+ const url = new URL(value);
325062
+ url.hash = "";
325063
+ return `${url.origin.toLowerCase()}${url.pathname}${url.search}`;
325064
+ } catch {
325065
+ return value.trim();
325066
+ }
325067
+ }
325068
+ function safeSegment(value, fallback) {
325069
+ const safe = value.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 120);
325070
+ return safe || fallback;
325071
+ }
325072
+ function stableVariables(testVariables) {
325073
+ return Object.entries(testVariables).filter(([key]) => !(0, auth_recipe_js_1.isReservedRunnerEnvVar)(key)).sort(([a], [b]) => a.localeCompare(b));
325074
+ }
325075
+ function localAuthStateFingerprint(input) {
325076
+ const payload = JSON.stringify({
325077
+ version: 1,
325078
+ projectId: input.projectId,
325079
+ serverOrigin: normalizeOrigin(input.serverUrl),
325080
+ appUrl: normalizeAppUrl(input.baseUrl),
325081
+ testVariables: stableVariables(input.testVariables)
325082
+ });
325083
+ const key = input.authToken || `validate-local-auth:${input.projectId}`;
325084
+ return (0, node_crypto_1.createHmac)("sha256", key).update(payload).digest("hex").slice(0, 24);
325085
+ }
325086
+ function ensurePrivateDirectory2(dir) {
325087
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true, mode: 448 });
325088
+ try {
325089
+ (0, node_fs_1.chmodSync)(dir, 448);
325090
+ } catch {
325091
+ }
325092
+ }
325093
+ function removeFile(path) {
325094
+ try {
325095
+ (0, node_fs_1.unlinkSync)(path);
325096
+ } catch {
325097
+ }
325098
+ }
325099
+ function readLockOwner(path) {
325100
+ try {
325101
+ const parsed = JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
325102
+ return typeof parsed.token === "string" && parsed.token.length > 0 && Number.isSafeInteger(parsed.pid) && (parsed.pid ?? 0) > 0 ? { token: parsed.token, pid: parsed.pid } : void 0;
325103
+ } catch {
325104
+ return void 0;
325105
+ }
325106
+ }
325107
+ function processIsAlive(pid) {
325108
+ try {
325109
+ process.kill(pid, 0);
325110
+ return true;
325111
+ } catch (error2) {
325112
+ return error2.code === "EPERM";
325113
+ }
325114
+ }
325115
+ function staleLockCanBeReaped(path, now, staleMs) {
325116
+ try {
325117
+ const ageMs = now - (0, node_fs_1.statSync)(path).mtimeMs;
325118
+ if (ageMs < staleMs)
325119
+ return false;
325120
+ const owner = readLockOwner(path);
325121
+ if (owner) {
325122
+ if (!processIsAlive(owner.pid))
325123
+ return true;
325124
+ return ageMs >= Math.max(staleMs * 5, exports.LOCAL_AUTH_STATE_LOCK_ABSOLUTE_STALE_MS);
325125
+ }
325126
+ return ageMs >= staleMs * 5;
325127
+ } catch {
325128
+ return false;
325129
+ }
325130
+ }
325131
+ function isRecord(value) {
325132
+ return !!value && typeof value === "object" && !Array.isArray(value);
325133
+ }
325134
+ function isValidStorageState(value) {
325135
+ if (!isRecord(value))
325136
+ return false;
325137
+ const cookies = value.cookies;
325138
+ const origins = value.origins;
325139
+ if (!Array.isArray(cookies) || !Array.isArray(origins))
325140
+ return false;
325141
+ const cookiesValid = cookies.every((cookie) => {
325142
+ if (!isRecord(cookie))
325143
+ return false;
325144
+ return typeof cookie.name === "string" && typeof cookie.value === "string" && typeof cookie.domain === "string" && typeof cookie.path === "string" && typeof cookie.expires === "number" && Number.isFinite(cookie.expires) && typeof cookie.httpOnly === "boolean" && typeof cookie.secure === "boolean" && (cookie.sameSite === "Strict" || cookie.sameSite === "Lax" || cookie.sameSite === "None");
325145
+ });
325146
+ if (!cookiesValid)
325147
+ return false;
325148
+ const originsValid = origins.every((origin) => {
325149
+ if (!isRecord(origin) || typeof origin.origin !== "string" || !Array.isArray(origin.localStorage))
325150
+ return false;
325151
+ return origin.localStorage.every((entry) => isRecord(entry) && typeof entry.name === "string" && typeof entry.value === "string");
325152
+ });
325153
+ if (!originsValid)
325154
+ return false;
325155
+ return cookies.length > 0 || origins.some((origin) => origin.localStorage.length > 0);
325156
+ }
325157
+ function isValidStorageStateJson(value) {
325158
+ try {
325159
+ return isValidStorageState(JSON.parse(value));
325160
+ } catch {
325161
+ return false;
325162
+ }
325163
+ }
325164
+ function isStorageStateShape(value) {
325165
+ if (!value || typeof value !== "object")
325166
+ return false;
325167
+ return isValidStorageState(value);
325168
+ }
325169
+ function isValidLocalAuthStateFile(path) {
325170
+ try {
325171
+ return isStorageStateShape(JSON.parse((0, node_fs_1.readFileSync)(path, "utf8")));
325172
+ } catch {
325173
+ return false;
325174
+ }
325175
+ }
325176
+ function cleanupLocalAuthStateFiles(runsDir, projectId, now = Date.now(), maxAgeMs = exports.LOCAL_AUTH_STATE_MAX_AGE_MS) {
325177
+ ensurePrivateDirectory2(runsDir);
325178
+ const projectSegment = safeSegment(projectId, "default");
325179
+ const legacyName = `auth-state-${projectSegment}.json`;
325180
+ const prefix = `auth-state-${projectSegment}-`;
325181
+ for (const name of (0, node_fs_1.readdirSync)(runsDir)) {
325182
+ if (name === legacyName) {
325183
+ removeFile((0, node_path_1.join)(runsDir, name));
325184
+ continue;
325185
+ }
325186
+ if (!name.startsWith(prefix))
325187
+ continue;
325188
+ const path = (0, node_path_1.join)(runsDir, name);
325189
+ try {
325190
+ if (now - (0, node_fs_1.statSync)(path).mtimeMs >= maxAgeMs) {
325191
+ if (!name.endsWith(".lock") || staleLockCanBeReaped(path, now, maxAgeMs))
325192
+ removeFile(path);
325193
+ }
325194
+ } catch {
325195
+ }
325196
+ }
325197
+ }
325198
+ function cleanupExpiredLocalAuthStateFiles2(runsDir, now = Date.now(), maxAgeMs = exports.LOCAL_AUTH_STATE_MAX_AGE_MS) {
325199
+ ensurePrivateDirectory2(runsDir);
325200
+ for (const name of (0, node_fs_1.readdirSync)(runsDir)) {
325201
+ if (!name.startsWith("auth-state-"))
325202
+ continue;
325203
+ const path = (0, node_path_1.join)(runsDir, name);
325204
+ try {
325205
+ if (now - (0, node_fs_1.statSync)(path).mtimeMs >= maxAgeMs) {
325206
+ if (!name.endsWith(".lock") || staleLockCanBeReaped(path, now, maxAgeMs))
325207
+ removeFile(path);
325208
+ }
325209
+ } catch {
325210
+ }
325211
+ }
325212
+ }
325213
+ function localAuthStatePaths2(runsDir, input, runId) {
325214
+ cleanupLocalAuthStateFiles(runsDir, input.projectId);
325215
+ const projectSegment = safeSegment(input.projectId, "default");
325216
+ const runSegment = safeSegment(runId, `${process.pid}`);
325217
+ const fingerprint = localAuthStateFingerprint(input);
325218
+ const cachePath = (0, node_path_1.join)(runsDir, `auth-state-${projectSegment}-${fingerprint}.json`);
325219
+ return {
325220
+ cachePath,
325221
+ capturePath: `${cachePath}.capture-${runSegment}-${process.pid}.tmp`,
325222
+ fingerprint,
325223
+ lockKey: `${normalizeOrigin(input.serverUrl)}:${projectSegment}:${fingerprint}`,
325224
+ lockPath: `${cachePath}.lock`
325225
+ };
325226
+ }
325227
+ function getValidLocalAuthState2(path, now = Date.now(), maxAgeMs = exports.LOCAL_AUTH_STATE_MAX_AGE_MS) {
325228
+ if (!(0, node_fs_1.existsSync)(path))
325229
+ return void 0;
325230
+ try {
325231
+ if (now - (0, node_fs_1.statSync)(path).mtimeMs >= maxAgeMs || !isValidLocalAuthStateFile(path)) {
325232
+ removeFile(path);
325233
+ return void 0;
325234
+ }
325235
+ return path;
325236
+ } catch {
325237
+ return void 0;
325238
+ }
325239
+ }
325240
+ async function waitForValidLocalAuthState(path, timeoutMs = exports.LOCAL_AUTH_STATE_WAIT_MS, pollMs = 100) {
325241
+ const deadline = Date.now() + Math.max(0, timeoutMs);
325242
+ do {
325243
+ const valid = getValidLocalAuthState2(path);
325244
+ if (valid)
325245
+ return valid;
325246
+ const remaining = deadline - Date.now();
325247
+ if (remaining <= 0)
325248
+ return void 0;
325249
+ await new Promise((resolve2) => setTimeout(resolve2, Math.min(Math.max(10, pollMs), remaining)));
325250
+ } while (true);
325251
+ }
325252
+ async function waitForLocalAuthStateProducer2(path, lockKey, startupGraceMs = 2e3, maxWaitMs = exports.LOCAL_AUTH_STATE_WAIT_MS, pollMs = 100, lockPath) {
325253
+ const startedAt = Date.now();
325254
+ const producerIsActive = () => lockTails.has(lockKey) || !!lockPath && (0, node_fs_1.existsSync)(lockPath);
325255
+ let sawProducer = producerIsActive();
325256
+ do {
325257
+ const valid = getValidLocalAuthState2(path);
325258
+ if (valid)
325259
+ return valid;
325260
+ const producerActive = producerIsActive();
325261
+ if (producerActive)
325262
+ sawProducer = true;
325263
+ const elapsed = Date.now() - startedAt;
325264
+ if (sawProducer && !producerActive)
325265
+ return void 0;
325266
+ if (!sawProducer && elapsed >= Math.max(0, startupGraceMs))
325267
+ return void 0;
325268
+ if (elapsed >= Math.max(0, maxWaitMs))
325269
+ return void 0;
325270
+ await new Promise((resolve2) => setTimeout(resolve2, Math.max(10, pollMs)));
325271
+ } while (true);
325272
+ }
325273
+ function promoteLocalAuthStateCapture2(capturePath, cachePath, replaceExisting = false, lease) {
325274
+ if (lease && !lease.isOwner()) {
325275
+ removeFile(capturePath);
325276
+ return false;
325277
+ }
325278
+ if (!(0, node_fs_1.existsSync)(capturePath) || !isValidLocalAuthStateFile(capturePath)) {
325279
+ removeFile(capturePath);
325280
+ return false;
325281
+ }
325282
+ try {
325283
+ (0, node_fs_1.chmodSync)(capturePath, 384);
325284
+ } catch {
325285
+ }
325286
+ try {
325287
+ if (lease && !lease.isOwner()) {
325288
+ removeFile(capturePath);
325289
+ return false;
325290
+ }
325291
+ if ((0, node_fs_1.existsSync)(cachePath)) {
325292
+ if (!replaceExisting && isValidLocalAuthStateFile(cachePath)) {
325293
+ removeFile(capturePath);
325294
+ return true;
325295
+ }
325296
+ removeFile(cachePath);
325297
+ }
325298
+ if (lease && !lease.isOwner()) {
325299
+ removeFile(capturePath);
325300
+ return false;
325301
+ }
325302
+ (0, node_fs_1.renameSync)(capturePath, cachePath);
325303
+ try {
325304
+ (0, node_fs_1.chmodSync)(cachePath, 384);
325305
+ } catch {
325306
+ }
325307
+ return true;
325308
+ } catch {
325309
+ const winnerIsValid = (0, node_fs_1.existsSync)(cachePath) && isValidLocalAuthStateFile(cachePath);
325310
+ removeFile(capturePath);
325311
+ return winnerIsValid;
325312
+ }
325313
+ }
325314
+ function discardLocalAuthStateCapture2(capturePath) {
325315
+ removeFile(capturePath);
325316
+ }
325317
+ var lockTails = /* @__PURE__ */ new Map();
325318
+ function publishCompleteLockOwner(lockPath, owner, linkFile) {
325319
+ const metadata = JSON.stringify(owner);
325320
+ const tempPath = `${lockPath}.${owner.pid}.${owner.token}.tmp`;
325321
+ let createdLock = false;
325322
+ try {
325323
+ (0, node_fs_1.writeFileSync)(tempPath, metadata, { flag: "wx", mode: 384 });
325324
+ try {
325325
+ linkFile(tempPath, lockPath);
325326
+ createdLock = true;
325327
+ return (0, node_fs_1.openSync)(lockPath, "r+");
325328
+ } catch (error2) {
325329
+ const code = error2.code;
325330
+ if (!["EPERM", "EACCES", "EXDEV", "ENOSYS", "ENOTSUP", "EOPNOTSUPP"].includes(code ?? "")) {
325331
+ throw error2;
325332
+ }
325333
+ let fd;
325334
+ try {
325335
+ fd = (0, node_fs_1.openSync)(lockPath, "wx+", 384);
325336
+ createdLock = true;
325337
+ (0, node_fs_1.writeFileSync)(fd, metadata, { encoding: "utf8" });
325338
+ (0, node_fs_1.fsyncSync)(fd);
325339
+ return fd;
325340
+ } catch (fallbackError) {
325341
+ if (fd !== void 0) {
325342
+ try {
325343
+ (0, node_fs_1.closeSync)(fd);
325344
+ } catch {
325345
+ }
325346
+ }
325347
+ if (createdLock)
325348
+ removeFile(lockPath);
325349
+ throw fallbackError;
325350
+ }
325351
+ }
325352
+ } catch (error2) {
325353
+ if (createdLock && readLockOwner(lockPath)?.token === owner.token)
325354
+ removeFile(lockPath);
325355
+ throw error2;
325356
+ } finally {
325357
+ removeFile(tempPath);
325358
+ }
325359
+ }
325360
+ async function acquireExclusiveFileLock(lockPath, linkFile = node_fs_1.linkSync) {
325361
+ ensurePrivateDirectory2((0, node_path_1.dirname)(lockPath));
325362
+ const startedAt = Date.now();
325363
+ while (true) {
325364
+ try {
325365
+ const ownerToken = (0, node_crypto_1.randomBytes)(24).toString("base64url");
325366
+ const fd = publishCompleteLockOwner(lockPath, { token: ownerToken, pid: process.pid }, linkFile);
325367
+ const ownsPath = () => {
325368
+ try {
325369
+ const owner = readLockOwner(lockPath);
325370
+ return owner?.token === ownerToken && owner.pid === process.pid;
325371
+ } catch {
325372
+ return false;
325373
+ }
325374
+ };
325375
+ const heartbeat = setInterval(() => {
325376
+ try {
325377
+ if (!ownsPath()) {
325378
+ clearInterval(heartbeat);
325379
+ return;
325380
+ }
325381
+ const now = /* @__PURE__ */ new Date();
325382
+ (0, node_fs_1.futimesSync)(fd, now, now);
325383
+ } catch {
325384
+ clearInterval(heartbeat);
325385
+ }
325386
+ }, Math.max(1e3, Math.floor(exports.LOCAL_AUTH_STATE_LOCK_STALE_MS / 3)));
325387
+ heartbeat.unref();
325388
+ let released = false;
325389
+ const release = (() => {
325390
+ if (released)
325391
+ return;
325392
+ released = true;
325393
+ clearInterval(heartbeat);
325394
+ try {
325395
+ if (ownsPath())
325396
+ removeFile(lockPath);
325397
+ } catch {
325398
+ }
325399
+ try {
325400
+ (0, node_fs_1.closeSync)(fd);
325401
+ } catch {
325402
+ }
325403
+ });
325404
+ release.isOwner = () => !released && ownsPath();
325405
+ return release;
325406
+ } catch (error2) {
325407
+ const code = error2.code;
325408
+ if (code !== "EEXIST")
325409
+ throw error2;
325410
+ try {
325411
+ if (staleLockCanBeReaped(lockPath, Date.now(), exports.LOCAL_AUTH_STATE_LOCK_STALE_MS)) {
325412
+ removeFile(lockPath);
325413
+ continue;
325414
+ }
325415
+ } catch {
325416
+ continue;
325417
+ }
325418
+ if (Date.now() - startedAt >= exports.LOCAL_AUTH_STATE_WAIT_MS) {
325419
+ throw new Error(`Timed out waiting for auth-state lock: ${(0, node_path_1.basename)(lockPath)}`);
325420
+ }
325421
+ await new Promise((resolve2) => setTimeout(resolve2, 100));
325422
+ }
325423
+ }
325424
+ }
325425
+ async function acquireLocalAuthStateLock2(key, lockPath, linkFile = node_fs_1.linkSync) {
325426
+ const previous = lockTails.get(key) ?? Promise.resolve();
325427
+ let unlockCurrent;
325428
+ const current = new Promise((resolve2) => {
325429
+ unlockCurrent = resolve2;
325430
+ });
325431
+ const tail = previous.then(() => current);
325432
+ lockTails.set(key, tail);
325433
+ await previous;
325434
+ let releaseFileLock;
325435
+ try {
325436
+ if (lockPath)
325437
+ releaseFileLock = await acquireExclusiveFileLock(lockPath, linkFile);
325438
+ } catch (error2) {
325439
+ unlockCurrent();
325440
+ if (lockTails.get(key) === tail)
325441
+ lockTails.delete(key);
325442
+ throw error2;
325443
+ }
325444
+ let released = false;
325445
+ const release = (() => {
325446
+ if (released)
325447
+ return;
325448
+ released = true;
325449
+ releaseFileLock?.();
325450
+ unlockCurrent();
325451
+ if (lockTails.get(key) === tail)
325452
+ lockTails.delete(key);
325453
+ });
325454
+ release.isOwner = () => !released && (releaseFileLock?.isOwner() ?? true);
325455
+ return release;
325456
+ }
325457
+ function localAuthStateFilename(path) {
325458
+ return (0, node_path_1.basename)(path);
325459
+ }
325460
+ }
325461
+ });
325462
+
324797
325463
  // ../../node_modules/adm-zip/util/constants.js
324798
325464
  var require_constants5 = __commonJS({
324799
325465
  "../../node_modules/adm-zip/util/constants.js"(exports, module) {
@@ -325107,7 +325773,7 @@ var require_utils2 = __commonJS({
325107
325773
  module.exports = Utils;
325108
325774
  Utils.prototype.makeDir = function(folder) {
325109
325775
  const self = this;
325110
- function mkdirSync5(fpath) {
325776
+ function mkdirSync4(fpath) {
325111
325777
  let resolvedPath = fpath.split(self.sep)[0];
325112
325778
  fpath.split(self.sep).forEach(function(name) {
325113
325779
  if (!name || name.substr(-1, 1) === ":") return;
@@ -325125,7 +325791,7 @@ var require_utils2 = __commonJS({
325125
325791
  if (stat && stat.isFile()) throw Errors.FILE_IN_THE_WAY(`"${resolvedPath}"`);
325126
325792
  });
325127
325793
  }
325128
- mkdirSync5(folder);
325794
+ mkdirSync4(folder);
325129
325795
  };
325130
325796
  Utils.prototype.writeFileTo = function(path, content, overwrite, attr) {
325131
325797
  const self = this;
@@ -337049,6 +337715,8 @@ var require_dist2 = __commonJS({
337049
337715
  __exportStar(require_discover_agent(), exports);
337050
337716
  __exportStar(require_browser_pool(), exports);
337051
337717
  __exportStar(require_resource_governor(), exports);
337718
+ __exportStar(require_local_auth_state(), exports);
337719
+ __exportStar(require_run_callback_proxy(), exports);
337052
337720
  __exportStar(require_discover_explorer(), exports);
337053
337721
  __exportStar(require_discover_feature_explorer(), exports);
337054
337722
  __exportStar(require_discover_planner(), exports);
@@ -337325,7 +337993,7 @@ var require_package4 = __commonJS({
337325
337993
  "package.json"(exports, module) {
337326
337994
  module.exports = {
337327
337995
  name: "@validate.qa/runner",
337328
- version: "1.0.41",
337996
+ version: "1.0.43",
337329
337997
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
337330
337998
  bin: {
337331
337999
  "validate-runner": "dist/cli.js"
@@ -337333,7 +338001,7 @@ var require_package4 = __commonJS({
337333
338001
  scripts: {
337334
338002
  build: "tsup",
337335
338003
  dev: "tsx src/cli.ts",
337336
- test: "node --import tsx --test src/version-check.test.ts",
338004
+ test: "node --import tsx --test src/version-check.test.ts src/services/auth-state.test.ts",
337337
338005
  "test:mobile": "node --import tsx --test --test-concurrency=2 src/runner.mobile-ai-env.test.ts src/mobile-device-queue.test.ts src/device-lease.test.ts src/result-payload-budget.test.ts src/services/appium-*.test.ts src/services/*mobile*.test.ts src/services/mobile/*.test.ts",
337338
338006
  prepublishOnly: "npm run build"
337339
338007
  },
@@ -337412,7 +338080,6 @@ function getConfigDir() {
337412
338080
  }
337413
338081
 
337414
338082
  // src/runner.ts
337415
- import { existsSync as existsSync3, statSync, mkdirSync as mkdirSync4 } from "fs";
337416
338083
  import { join as join6 } from "path";
337417
338084
  import { homedir as homedir3 } from "os";
337418
338085
  import { createHash } from "crypto";
@@ -337440,23 +338107,33 @@ var import_runner_core6 = __toESM(require_dist2());
337440
338107
 
337441
338108
  // src/runner.ts
337442
338109
  var import_runner_core11 = __toESM(require_dist2());
338110
+ var import_runner_core12 = __toESM(require_dist2());
337443
338111
 
337444
338112
  // src/services/auth-state.ts
338113
+ init_dist();
337445
338114
  var INLINE_LOGIN_TAG = "@inline-login";
337446
338115
  var GUARDED_LOGIN_TAG = "@auth-guarded";
337447
338116
  function sharedAuthInjectEnabled() {
337448
- return process.env.SHARED_AUTH_INJECT === "true";
338117
+ return isSharedAuthInjectEnabled();
338118
+ }
338119
+ function hasGeneratedAuthGuard(playwrightCode) {
338120
+ if (!playwrightCode) return false;
338121
+ return /\/\/ >>> VALIDATE_QA_AUTH v\d+\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\d+\b/.test(playwrightCode);
338122
+ }
338123
+ function shouldCaptureAuthState(tags, playwrightCode) {
338124
+ return sharedAuthInjectEnabled() && Array.isArray(tags) && tags.includes("@needs-auth") && tags.includes(GUARDED_LOGIN_TAG) && !tags.includes("@auth") && hasGeneratedAuthGuard(playwrightCode);
337449
338125
  }
337450
338126
  function hasInlineLoginSteps(playwrightCode) {
337451
338127
  if (!playwrightCode) return false;
337452
338128
  return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
337453
338129
  }
337454
338130
  function shouldUseAuthState(tags, playwrightCode) {
338131
+ if (!sharedAuthInjectEnabled()) return false;
337455
338132
  if (!Array.isArray(tags)) return false;
337456
338133
  if (tags.includes("@auth")) return false;
337457
338134
  if (tags.includes("@api")) return true;
337458
338135
  if (!tags.includes("@needs-auth")) return false;
337459
- if (sharedAuthInjectEnabled() && tags.includes(GUARDED_LOGIN_TAG)) return true;
338136
+ if (tags.includes(GUARDED_LOGIN_TAG) && hasGeneratedAuthGuard(playwrightCode)) return true;
337460
338137
  const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
337461
338138
  const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(playwrightCode);
337462
338139
  return !tags.includes(INLINE_LOGIN_TAG) && !hasLegacyInlineLogin;
@@ -344169,8 +344846,8 @@ function withMediaReferences(result, refs) {
344169
344846
 
344170
344847
  // src/runner.ts
344171
344848
  init_dist();
344172
- var import_runner_core12 = __toESM(require_dist2());
344173
344849
  var import_runner_core13 = __toESM(require_dist2());
344850
+ var import_runner_core14 = __toESM(require_dist2());
344174
344851
  function fingerprintTest(test) {
344175
344852
  const code = test.framework === "APPIUM" ? [test.appiumCode ?? "", JSON.stringify(test.steps ?? [])].join("\n--steps--\n") : test.playwrightCode ?? "";
344176
344853
  return createHash("sha256").update(test.name).update("\n--suite--\n").update(test.suiteName ?? "").update("\n--type--\n").update(test.testType ?? "").update("\n--framework--\n").update(test.framework ?? "").update("\n--code--\n").update(code).digest("hex").slice(0, 24);
@@ -344294,21 +344971,11 @@ var log = {
344294
344971
  warn: (msg) => console.log(`${C2.yellow}\u26A0${C2.reset} ${msg}`),
344295
344972
  dim: (msg) => console.log(`${C2.gray}${msg}${C2.reset}`)
344296
344973
  };
344297
- var AUTH_MAX_AGE_MS = 60 * 60 * 1e3;
344298
344974
  function getRunsDir() {
344299
344975
  const dir = process.env.RUNS_DIR || join6(homedir3(), ".validate.qa", "runs");
344300
- if (!existsSync3(dir)) mkdirSync4(dir, { recursive: true });
344976
+ (0, import_runner_core12.ensurePrivateDirectory)(dir);
344301
344977
  return dir;
344302
344978
  }
344303
- function authStatePath(projectId) {
344304
- return join6(getRunsDir(), `auth-state-${projectId || "default"}.json`);
344305
- }
344306
- function getValidAuthState(projectId) {
344307
- const path = authStatePath(projectId);
344308
- if (!existsSync3(path)) return void 0;
344309
- const age = Date.now() - statSync(path).mtimeMs;
344310
- return age < AUTH_MAX_AGE_MS ? path : void 0;
344311
- }
344312
344979
  function buildHeaders(authToken, projectId) {
344313
344980
  return {
344314
344981
  "Content-Type": "application/json",
@@ -344357,7 +345024,13 @@ async function claimRun(serverUrl, headers, runId) {
344357
345024
  if (res.status === 429) return false;
344358
345025
  if (!res.ok) throw new Error(`Claim failed: ${res.status}`);
344359
345026
  const body = await res.json();
344360
- return body.claimed;
345027
+ if (!body.claimed) {
345028
+ throw new Error("Claim response did not confirm ownership");
345029
+ }
345030
+ if (typeof body.claimEpoch !== "number" || !Number.isSafeInteger(body.claimEpoch) || body.claimEpoch < 0) {
345031
+ throw new Error("Claim response did not include a valid ownership epoch");
345032
+ }
345033
+ return body.claimEpoch;
344361
345034
  }
344362
345035
  async function postResult(serverUrl, headers, runId, result) {
344363
345036
  const MAX_ATTEMPTS = 3;
@@ -344525,12 +345198,12 @@ async function postCaptureResult(serverUrl, headers, runId, result) {
344525
345198
  });
344526
345199
  if (!res.ok) throw new Error(`Failed to post capture result: ${res.status}`);
344527
345200
  }
344528
- async function postCredentialRotation(serverUrl, headers, runId, body) {
345201
+ async function postCredentialRotation(serverUrl, headers, runId, body, claimEpoch) {
344529
345202
  try {
344530
345203
  const res = await fetch(`${serverUrl}/api/runner/runs/${runId}/credential`, {
344531
345204
  method: "POST",
344532
345205
  headers,
344533
- body: JSON.stringify(body),
345206
+ body: JSON.stringify({ ...body, ...claimEpoch !== void 0 ? { claimEpoch } : {} }),
344534
345207
  signal: AbortSignal.timeout(3e4)
344535
345208
  });
344536
345209
  if (!res.ok) {
@@ -344867,6 +345540,12 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
344867
345540
  liveBrowserHandle,
344868
345541
  onLiveBrowserSnapshot: requestLiveBrowserHeartbeat
344869
345542
  });
345543
+ const callbackContext = {
345544
+ runId: run2.runId,
345545
+ serverUrl: opts.serverUrl,
345546
+ authHeader: headers.Authorization,
345547
+ claimEpoch: run2.claimEpoch
345548
+ };
344870
345549
  const startTime = Date.now();
344871
345550
  try {
344872
345551
  if (mobileLeaseError) {
@@ -344896,7 +345575,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
344896
345575
  log.fail(`PRE-FLIGHT FAILED - ${preflightError}`);
344897
345576
  return;
344898
345577
  }
344899
- const preflight = await (0, import_runner_core12.resolveBaseUrlPreflight)(run2.baseUrl);
345578
+ const preflight = await (0, import_runner_core13.resolveBaseUrlPreflight)(run2.baseUrl);
344900
345579
  if (preflight.error) {
344901
345580
  await postResult(opts.serverUrl, headers, run2.runId, { status: "ERROR", error: preflight.error });
344902
345581
  log.fail(`PRE-FLIGHT FAILED - ${preflight.error}`);
@@ -344913,31 +345592,72 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
344913
345592
  log.fail("ERROR - No auth setup code");
344914
345593
  return;
344915
345594
  }
344916
- const runAuthPath = authStatePath(run2.projectId || opts.projectId || "default");
344917
- log.info(`Auth setup: saving session state to ${runAuthPath}`);
344918
- const result2 = await runNativeWithLiveBrowser({
344919
- playwrightCode: run2.playwrightCode,
345595
+ const sharedAuthEnabled = sharedAuthInjectEnabled();
345596
+ if ((0, import_runner_core12.shouldSkipDisabledAutomaticAuthSetup)(run2.authForceRun, run2.authVerify, sharedAuthEnabled)) {
345597
+ const duration2 = Date.now() - startTime;
345598
+ await postResult(opts.serverUrl, headers, run2.runId, {
345599
+ status: "PASSED",
345600
+ logs: "Automatic auth prewarm skipped: shared session reuse is disabled.",
345601
+ duration: duration2
345602
+ });
345603
+ log.info(`Auth prewarm skipped (${duration2}ms) - SHARED_AUTH_INJECT=false`);
345604
+ return;
345605
+ }
345606
+ const authPaths2 = sharedAuthEnabled ? (0, import_runner_core12.localAuthStatePaths)(getRunsDir(), {
345607
+ projectId: run2.projectId || opts.projectId || "default",
345608
+ serverUrl: opts.serverUrl,
344920
345609
  baseUrl: run2.baseUrl,
344921
- testName: "auth-setup",
344922
- testVariables: { ...run2.testVariables, AUTH_FILE: runAuthPath },
344923
- validateContext: {
344924
- runId: run2.runId,
344925
- serverUrl: opts.serverUrl,
344926
- authHeader: headers.Authorization
345610
+ testVariables: run2.testVariables,
345611
+ authToken: opts.authToken
345612
+ }, run2.runId) : void 0;
345613
+ const releaseAuthLock2 = authPaths2 ? await (0, import_runner_core12.acquireLocalAuthStateLock)(authPaths2.lockKey, authPaths2.lockPath) : void 0;
345614
+ try {
345615
+ if (authPaths2 && (0, import_runner_core12.shouldSkipWarmAuthSetup)(
345616
+ run2.authForceRun,
345617
+ sharedAuthEnabled,
345618
+ !!(0, import_runner_core12.getValidLocalAuthState)(authPaths2.cachePath)
345619
+ )) {
345620
+ const duration3 = Date.now() - startTime;
345621
+ await postResult(opts.serverUrl, headers, run2.runId, {
345622
+ status: "PASSED",
345623
+ logs: "Auth setup skipped: the credential-bound local session is already warm.",
345624
+ duration: duration3
345625
+ });
345626
+ log.ok(`Auth setup skipped (${duration3}ms) - local session already warm`);
345627
+ return;
344927
345628
  }
344928
- });
344929
- const duration2 = Date.now() - startTime;
344930
- await postResult(opts.serverUrl, headers, run2.runId, {
344931
- status: result2.passed ? "PASSED" : "FAILED",
344932
- apiCalls: result2.apiCalls,
344933
- logs: result2.logs,
344934
- error: result2.error,
344935
- duration: duration2
344936
- });
344937
- log[result2.passed ? "ok" : "fail"](
344938
- `Auth setup ${result2.passed ? "PASSED" : "FAILED"} (${duration2}ms)${result2.error ? ` - ${result2.error}` : ""}`
344939
- );
344940
- return;
345629
+ log.info(run2.authVerify ? "Auth verification: replaying the current login recipe" : sharedAuthEnabled ? "Auth setup: capturing a credential-bound local session" : "Auth setup: running login with shared session reuse disabled");
345630
+ const capturePath = sharedAuthEnabled && !run2.authVerify ? authPaths2?.capturePath : void 0;
345631
+ const result2 = await runNativeWithLiveBrowser({
345632
+ playwrightCode: run2.playwrightCode,
345633
+ baseUrl: run2.baseUrl,
345634
+ testName: "auth-setup",
345635
+ testVariables: run2.testVariables,
345636
+ authStateCapturePath: capturePath,
345637
+ validateContext: callbackContext
345638
+ });
345639
+ const captured = !!capturePath && !!authPaths2 && result2.passed && !result2.suspiciousPass && (0, import_runner_core12.promoteLocalAuthStateCapture)(
345640
+ capturePath,
345641
+ authPaths2.cachePath,
345642
+ run2.authForceRun === true,
345643
+ releaseAuthLock2
345644
+ );
345645
+ const duration2 = Date.now() - startTime;
345646
+ await postResult(opts.serverUrl, headers, run2.runId, {
345647
+ status: result2.passed ? "PASSED" : "FAILED",
345648
+ apiCalls: result2.apiCalls,
345649
+ logs: result2.logs,
345650
+ error: result2.error,
345651
+ duration: duration2
345652
+ });
345653
+ log[result2.passed ? "ok" : "fail"](
345654
+ `Auth setup ${result2.passed ? "PASSED" : "FAILED"} (${duration2}ms)${captured ? " - session cached" : ""}${result2.error ? ` - ${result2.error}` : ""}`
345655
+ );
345656
+ return;
345657
+ } finally {
345658
+ if (authPaths2?.capturePath) (0, import_runner_core12.discardLocalAuthStateCapture)(authPaths2.capturePath);
345659
+ releaseAuthLock2?.();
345660
+ }
344941
345661
  }
344942
345662
  if (runMode === "batch-heal") {
344943
345663
  const tests = run2.batchHealTests ?? [];
@@ -344956,7 +345676,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
344956
345676
  const batchResult = await (0, import_runner_core10.executeGrokPerBatchHeal)(tests, run2.baseUrl, {
344957
345677
  testVariables: run2.testVariables,
344958
345678
  runNativeTest: runNativeWithLiveBrowser,
344959
- validateContext: { runId: run2.runId, serverUrl: opts.serverUrl, authHeader: headers.Authorization },
345679
+ validateContext: callbackContext,
344960
345680
  onLog: healLogStream.onLog
344961
345681
  });
344962
345682
  await healLogStream.flushNow();
@@ -344992,6 +345712,15 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
344992
345712
  );
344993
345713
  const describeKind = isSerialDescribe ? "serial" : "non-serial";
344994
345714
  log.info(`API test heal - ${subTestCount} sub-test(s) [${describeKind}], ${failingSubTestCount} budgeted, ${MAX_API_HEAL_ATTEMPTS} attempts`);
345715
+ const apiHealAuthPaths = shouldUseAuthState(run2.tags, run2.playwrightCode) ? (0, import_runner_core12.localAuthStatePaths)(getRunsDir(), {
345716
+ projectId: run2.projectId || opts.projectId,
345717
+ serverUrl: opts.serverUrl,
345718
+ baseUrl: run2.baseUrl,
345719
+ testVariables: run2.testVariables,
345720
+ authToken: opts.authToken
345721
+ }, run2.runId) : void 0;
345722
+ const apiHealStorageStatePath = apiHealAuthPaths ? (0, import_runner_core12.getValidLocalAuthState)(apiHealAuthPaths.cachePath) : void 0;
345723
+ if (apiHealStorageStatePath) log.info("API heal verification is using saved auth state");
344995
345724
  const stickyAppBugs = /* @__PURE__ */ new Map();
344996
345725
  const lastChangeByTitle = /* @__PURE__ */ new Map();
344997
345726
  let currentError = run2.lastFailureError ?? "Test failed";
@@ -345057,7 +345786,8 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
345057
345786
  playwrightCode: lastHealedCode,
345058
345787
  baseUrl: run2.baseUrl,
345059
345788
  testVariables: run2.testVariables,
345060
- validateContext: { runId: run2.runId, serverUrl: opts.serverUrl, authHeader: headers.Authorization }
345789
+ storageStatePath: apiHealStorageStatePath,
345790
+ validateContext: callbackContext
345061
345791
  });
345062
345792
  if (finalVerifyResult.passed) {
345063
345793
  log.ok(` Attempt ${attempt} terminal verification PASSED (${bugCount} app_bug, ${passCount} pass)`);
@@ -345077,7 +345807,9 @@ ${finalVerifyResult.logs ?? ""}`;
345077
345807
  finalVerifyResult = await runNativeWithLiveBrowser({
345078
345808
  playwrightCode: lastHealedCode,
345079
345809
  baseUrl: run2.baseUrl,
345080
- testVariables: run2.testVariables
345810
+ testVariables: run2.testVariables,
345811
+ storageStatePath: apiHealStorageStatePath,
345812
+ validateContext: callbackContext
345081
345813
  });
345082
345814
  const stepResults = finalVerifyResult.stepResults ?? [];
345083
345815
  const nonAppBugFails = stepResults.filter(
@@ -345127,7 +345859,8 @@ ${finalVerifyResult.logs ?? ""}`;
345127
345859
  playwrightCode: lastHealedCode,
345128
345860
  baseUrl: run2.baseUrl,
345129
345861
  testVariables: run2.testVariables,
345130
- validateContext: { runId: run2.runId, serverUrl: opts.serverUrl, authHeader: headers.Authorization }
345862
+ storageStatePath: apiHealStorageStatePath,
345863
+ validateContext: callbackContext
345131
345864
  });
345132
345865
  if (finalVerifyResult.passed) {
345133
345866
  log.ok(` Attempt ${attempt} verification PASSED`);
@@ -345180,7 +345913,7 @@ ${finalVerifyResult.logs ?? ""}`;
345180
345913
  if (classifyErrorMessage(composedError ?? null) === "config_issue") {
345181
345914
  apiHealStatus = "ERROR";
345182
345915
  } else {
345183
- const rlDetection = (0, import_runner_core12.detectRateLimitBlock)({
345916
+ const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
345184
345917
  testName: run2.testName ?? run2.testCaseId ?? void 0,
345185
345918
  tags: run2.tags ?? [],
345186
345919
  error: composedError ?? finalVerifyResult?.error ?? null,
@@ -345277,8 +346010,8 @@ ${finalVerifyResult.logs ?? ""}`;
345277
346010
  lastHealAttempt: run2.lastHealAttempt ?? null,
345278
346011
  lastHealedCode: run2.lastHealedCode ?? null,
345279
346012
  failureScreenshot: run2.failureScreenshot ?? null,
345280
- onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }),
345281
- validateContext: { runId: run2.runId, serverUrl: opts.serverUrl, authHeader: headers.Authorization },
346013
+ onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }, run2.claimEpoch),
346014
+ validateContext: callbackContext,
345282
346015
  liveBrowserHandle,
345283
346016
  runNativeTest: runNativeWithLiveBrowser
345284
346017
  };
@@ -345320,7 +346053,7 @@ ${finalVerifyResult.logs ?? ""}`;
345320
346053
  let healStatus = result2.passed ? "PASSED" : isConfigIssue ? "ERROR" : "FAILED";
345321
346054
  let healError = result2.error;
345322
346055
  if (!result2.passed && !isConfigIssue) {
345323
- const rlDetection = (0, import_runner_core12.detectRateLimitBlock)({
346056
+ const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
345324
346057
  testName: run2.testName ?? run2.testCaseId ?? void 0,
345325
346058
  tags: run2.tags ?? [],
345326
346059
  error: result2.error,
@@ -345391,7 +346124,8 @@ ${finalVerifyResult.logs ?? ""}`;
345391
346124
  // Allow generated `persistCredential(...)` calls inside the suite to
345392
346125
  // POST back to the server with the runner's bearer token.
345393
346126
  validateServerUrl: opts.serverUrl,
345394
- validateRunnerAuthHeader: headers.Authorization
346127
+ validateRunnerAuthHeader: headers.Authorization,
346128
+ claimEpoch: run2.claimEpoch
345395
346129
  });
345396
346130
  const overall = result2.error && result2.testResults.length === 0 ? "ERROR" : result2.passed ? "PASSED" : "FAILED";
345397
346131
  const rr = await fetch(`${opts.serverUrl}/api/runner/runs/${run2.runId}/regression-result`, {
@@ -345446,7 +346180,7 @@ ${finalVerifyResult.logs ?? ""}`;
345446
346180
  playwrightCode: healedCode,
345447
346181
  baseUrl: run2.baseUrl,
345448
346182
  testVariables: run2.testVariables,
345449
- validateContext: { runId: run2.runId, serverUrl: opts.serverUrl, authHeader: headers.Authorization }
346183
+ validateContext: callbackContext
345450
346184
  });
345451
346185
  results.push({
345452
346186
  testCaseId: failedTest.testCaseId,
@@ -345509,8 +346243,8 @@ ${finalVerifyResult.logs ?? ""}`;
345509
346243
  onAICall: healAudit,
345510
346244
  modelOverride: run2.healerModel,
345511
346245
  consecutiveFails: failedTest.consecutiveFails,
345512
- onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }),
345513
- validateContext: { runId: run2.runId, serverUrl: opts.serverUrl, authHeader: headers.Authorization },
346246
+ onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }, run2.claimEpoch),
346247
+ validateContext: callbackContext,
345514
346248
  liveBrowserHandle,
345515
346249
  runNativeTest: runNativeWithLiveBrowser
345516
346250
  };
@@ -345715,6 +346449,13 @@ ${finalVerifyResult.logs ?? ""}`;
345715
346449
  }
345716
346450
  return;
345717
346451
  }
346452
+ ctx.onCredentialUpdate = async (input) => postCredentialRotation(
346453
+ opts.serverUrl,
346454
+ headers,
346455
+ run2.runId,
346456
+ { ...input, actor: "ai-explorer" },
346457
+ run2.claimEpoch
346458
+ );
345718
346459
  log.info(`Discovery (${ctx.mode}) on ${ctx.baseUrl}`);
345719
346460
  const { push: pushAudit, drain: drainAudit } = createAuditFlusher(
345720
346461
  `${opts.serverUrl}/api/runner/runs/${run2.runId}/ai-audit-batch`,
@@ -346417,7 +347158,7 @@ ${finalVerifyResult.logs ?? ""}`;
346417
347158
  featureDescription: ctx.featureDescription,
346418
347159
  featurePages: ctx.featurePages,
346419
347160
  appBrief: ctx.appBrief,
346420
- onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-capture" })
347161
+ onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-capture" }, run2.claimEpoch)
346421
347162
  });
346422
347163
  if (logBuffer.length > 0) {
346423
347164
  postCaptureLog(opts.serverUrl, headers, run2.runId, ctx.jobId, truncateTextTail(logBuffer.join("\n"), DISCOVERY_LOG_POST_MAX_CHARS, "capture log"));
@@ -346662,7 +347403,7 @@ ${finalVerifyResult.logs ?? ""}`;
346662
347403
  }
346663
347404
  return verify;
346664
347405
  };
346665
- return await (0, import_runner_core13.executeMobileLiveHeal)({
347406
+ return await (0, import_runner_core14.executeMobileLiveHeal)({
346666
347407
  steps: a.steps,
346667
347408
  target: a.target,
346668
347409
  testVariables: a.testVariables,
@@ -346679,7 +347420,7 @@ ${finalVerifyResult.logs ?? ""}`;
346679
347420
  }
346680
347421
  };
346681
347422
  log.info("\u{1FA7A} Mobile AI heal \u2014 Phase 1 (script) \u2192 Phase 2 (live device, lazy)...");
346682
- const heal = await (0, import_runner_core13.executeMobileTestWithHealing)(
347423
+ const heal = await (0, import_runner_core14.executeMobileTestWithHealing)(
346683
347424
  mobileSteps,
346684
347425
  healTarget,
346685
347426
  {
@@ -346692,7 +347433,7 @@ ${finalVerifyResult.logs ?? ""}`;
346692
347433
  healerOptions
346693
347434
  );
346694
347435
  const finalRes = nativeVerify.last;
346695
- const outcome = (0, import_runner_core13.computeMobileHealOutcome)({
347436
+ const outcome = (0, import_runner_core14.computeMobileHealOutcome)({
346696
347437
  heal,
346697
347438
  finalError: finalRes?.error ?? null,
346698
347439
  reproduceError,
@@ -346838,26 +347579,77 @@ ${result2.logs}`);
346838
347579
  }
346839
347580
  return;
346840
347581
  }
346841
- const storageStatePath = shouldUseAuthState(run2.tags, run2.playwrightCode) ? getValidAuthState(run2.projectId || opts.projectId) : void 0;
346842
- if (storageStatePath) {
346843
- log.info(`Using auth state for project ${run2.projectId || opts.projectId}`);
346844
- }
346845
- const result = await runNativeWithLiveBrowser({
346846
- playwrightCode: run2.playwrightCode,
347582
+ const projectId = run2.projectId || opts.projectId;
347583
+ const authStateExpected = shouldUseAuthState(run2.tags, run2.playwrightCode);
347584
+ const captureAuth = shouldCaptureAuthState(run2.tags, run2.playwrightCode);
347585
+ const authPaths = authStateExpected || captureAuth ? (0, import_runner_core12.localAuthStatePaths)(getRunsDir(), {
347586
+ projectId,
347587
+ serverUrl: opts.serverUrl,
346847
347588
  baseUrl: run2.baseUrl,
346848
347589
  testVariables: run2.testVariables,
346849
- storageStatePath,
346850
- validateContext: {
346851
- runId: run2.runId,
346852
- serverUrl: opts.serverUrl,
346853
- authHeader: headers.Authorization
347590
+ authToken: opts.authToken
347591
+ }, run2.runId) : void 0;
347592
+ let releaseAuthLock;
347593
+ let storageStatePath;
347594
+ if (authPaths && authStateExpected) {
347595
+ releaseAuthLock = await (0, import_runner_core12.acquireLocalAuthStateLock)(authPaths.lockKey, authPaths.lockPath);
347596
+ storageStatePath = (0, import_runner_core12.getValidLocalAuthState)(authPaths.cachePath);
347597
+ if (storageStatePath || !captureAuth) {
347598
+ releaseAuthLock();
347599
+ releaseAuthLock = void 0;
347600
+ }
347601
+ if (!storageStatePath && !captureAuth && sharedAuthInjectEnabled()) {
347602
+ log.info(`Waiting for the auth producer to warm project ${projectId}`);
347603
+ storageStatePath = await (0, import_runner_core12.waitForLocalAuthStateProducer)(
347604
+ authPaths.cachePath,
347605
+ authPaths.lockKey,
347606
+ 2e3,
347607
+ void 0,
347608
+ 100,
347609
+ authPaths.lockPath
347610
+ );
346854
347611
  }
346855
- });
347612
+ }
347613
+ if (storageStatePath) {
347614
+ log.info(`Using auth state for project ${projectId}`);
347615
+ }
347616
+ const runAuthPath = captureAuth ? authPaths?.capturePath : void 0;
347617
+ const result = await (async () => {
347618
+ try {
347619
+ return await runNativeWithLiveBrowser({
347620
+ playwrightCode: run2.playwrightCode,
347621
+ baseUrl: run2.baseUrl,
347622
+ testVariables: run2.testVariables,
347623
+ authStateCapturePath: runAuthPath,
347624
+ storageStatePath,
347625
+ validateContext: callbackContext
347626
+ });
347627
+ } finally {
347628
+ if (runAuthPath && authPaths) {
347629
+ if ((0, import_runner_core12.getValidLocalAuthState)(runAuthPath)) {
347630
+ const releasePromotionLock = releaseAuthLock ? void 0 : await (0, import_runner_core12.acquireLocalAuthStateLock)(authPaths.lockKey, authPaths.lockPath);
347631
+ try {
347632
+ const captured = (0, import_runner_core12.promoteLocalAuthStateCapture)(
347633
+ runAuthPath,
347634
+ authPaths.cachePath,
347635
+ !!storageStatePath,
347636
+ releaseAuthLock ?? releasePromotionLock
347637
+ );
347638
+ if (captured) log.info(`Captured auth state for project ${projectId}`);
347639
+ } finally {
347640
+ releasePromotionLock?.();
347641
+ }
347642
+ }
347643
+ (0, import_runner_core12.discardLocalAuthStateCapture)(runAuthPath);
347644
+ }
347645
+ releaseAuthLock?.();
347646
+ }
347647
+ })();
346856
347648
  const duration = Date.now() - startTime;
346857
347649
  let runStatus = result.passed ? "PASSED" : "FAILED";
346858
347650
  let runError = result.error;
346859
347651
  if (!result.passed) {
346860
- const rlDetection = (0, import_runner_core12.detectRateLimitBlock)({
347652
+ const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
346861
347653
  testName: run2.testName ?? run2.testCaseId ?? void 0,
346862
347654
  tags: run2.tags ?? [],
346863
347655
  error: result.error,
@@ -346918,6 +347710,12 @@ async function startRunner(opts) {
346918
347710
  process.env.AUTH_TOKEN = authToken;
346919
347711
  process.env.RUNNER_TOKEN = authToken;
346920
347712
  if (projectId) process.env.PROJECT_ID = projectId;
347713
+ (0, import_runner_core12.cleanupExpiredLocalAuthStateFiles)(getRunsDir());
347714
+ const authCleanupTimer = setInterval(
347715
+ () => (0, import_runner_core12.cleanupExpiredLocalAuthStateFiles)(getRunsDir()),
347716
+ import_runner_core12.LOCAL_AUTH_STATE_CLEANUP_INTERVAL_MS
347717
+ );
347718
+ authCleanupTimer.unref?.();
346921
347719
  const headers = buildHeaders(authToken, projectId);
346922
347720
  console.log("");
346923
347721
  console.log(`${C2.bold}${C2.cyan}validate.qa Local Runner${C2.reset}`);
@@ -347026,7 +347824,7 @@ async function startRunner(opts) {
347026
347824
  capture: envInt("MAX_CAPTURE", DEFAULT_TYPE_LIMITS.capture),
347027
347825
  run: envInt("MAX_RUN", DEFAULT_TYPE_LIMITS.run)
347028
347826
  };
347029
- const RESOURCE_GOVERNOR_OPTIONS = (0, import_runner_core12.createResourceGovernorOptions)({
347827
+ const RESOURCE_GOVERNOR_OPTIONS = (0, import_runner_core13.createResourceGovernorOptions)({
347030
347828
  isCloud: IS_CLOUD,
347031
347829
  maxConcurrent: MAX_CONCURRENT,
347032
347830
  typeLimits: TYPE_LIMITS
@@ -347194,8 +347992,8 @@ async function startRunner(opts) {
347194
347992
  android: devices.some((d) => d.platform === "ANDROID" && d.isAvailable && d.state === "BOOTED")
347195
347993
  } : { web: true, ios: false, android: false };
347196
347994
  const bridge = enableMobile ? getBridgeState() : void 0;
347197
- const resourceStatus = (0, import_runner_core12.getResourceGovernorStatus)(RESOURCE_GOVERNOR_OPTIONS, Object.fromEntries(activeByType));
347198
- const processInventory = await (0, import_runner_core12.getBrowserProcessInventory)();
347995
+ const resourceStatus = (0, import_runner_core13.getResourceGovernorStatus)(RESOURCE_GOVERNOR_OPTIONS, Object.fromEntries(activeByType));
347996
+ const processInventory = await (0, import_runner_core13.getBrowserProcessInventory)();
347199
347997
  lastInventoryPids = new Set(processInventory.processes.map((p) => p.pid));
347200
347998
  lastInventoryKinds.clear();
347201
347999
  for (const p of processInventory.processes) lastInventoryKinds.set(p.pid, p.kind);
@@ -347225,7 +348023,7 @@ async function startRunner(opts) {
347225
348023
  resourceSnapshot: resourceStatus.snapshot,
347226
348024
  resourceGovernor: resourceStatus.governor,
347227
348025
  processInventory,
347228
- aiQueue: (0, import_runner_core12.getRunnerAIQueueStats)(),
348026
+ aiQueue: (0, import_runner_core13.getRunnerAIQueueStats)(),
347229
348027
  liveBrowsers: import_runner_core11.liveBrowserRegistry.snapshotForHeartbeat(),
347230
348028
  // Pre-flight checks (ANDROID_HOME, Xcode, Appium drivers, adb, etc).
347231
348029
  // Cached & cheap — see getCachedDoctor above.
@@ -347336,7 +348134,7 @@ async function startRunner(opts) {
347336
348134
  let pidsWatchdogWarned = false;
347337
348135
  const pidsWatchdog = setInterval(() => {
347338
348136
  if (shuttingDown) return;
347339
- const snapshot = (0, import_runner_core12.getResourceSnapshot)();
348137
+ const snapshot = (0, import_runner_core13.getResourceSnapshot)();
347340
348138
  const pct = snapshot.pidsPct;
347341
348139
  if (pct == null || !Number.isFinite(pct)) return;
347342
348140
  if (pct >= PIDS_RECYCLE_PCT) {
@@ -347376,7 +348174,7 @@ async function startRunner(opts) {
347376
348174
  log.dim(` Deferred ${run2.runId} (project ${runProject} at capacity ${getActiveForProject(runProject)}/${projectLimit})`);
347377
348175
  continue;
347378
348176
  }
347379
- const admission = (0, import_runner_core12.evaluateRunAdmission)(RESOURCE_GOVERNOR_OPTIONS, {
348177
+ const admission = (0, import_runner_core13.evaluateRunAdmission)(RESOURCE_GOVERNOR_OPTIONS, {
347380
348178
  mode: runMode,
347381
348179
  activeRuns,
347382
348180
  activeByType: Object.fromEntries(activeByType),
@@ -347454,9 +348252,9 @@ async function startRunner(opts) {
347454
348252
  leasedDeviceId = freeDeviceId;
347455
348253
  leasedDeviceIds.set(freeDeviceId, run2.runId);
347456
348254
  }
347457
- let claimed = false;
348255
+ let claimEpoch = false;
347458
348256
  try {
347459
- claimed = await claimRun(serverUrl, headers, run2.runId);
348257
+ claimEpoch = await claimRun(serverUrl, headers, run2.runId);
347460
348258
  } catch (err) {
347461
348259
  if (leasedDeviceId) {
347462
348260
  leasedDeviceIds.delete(leasedDeviceId);
@@ -347464,7 +348262,7 @@ async function startRunner(opts) {
347464
348262
  }
347465
348263
  throw err;
347466
348264
  }
347467
- if (!claimed) {
348265
+ if (claimEpoch === false) {
347468
348266
  log.dim(` Skipped ${run2.runId} (already claimed)`);
347469
348267
  if (leasedDeviceId) {
347470
348268
  leasedDeviceIds.delete(leasedDeviceId);
@@ -347472,6 +348270,7 @@ async function startRunner(opts) {
347472
348270
  }
347473
348271
  continue;
347474
348272
  }
348273
+ run2.claimEpoch = claimEpoch;
347475
348274
  activeRuns++;
347476
348275
  incrementType(runMode);
347477
348276
  incrementProject(runProject);