@validate.qa/runner 1.0.101 → 1.0.103

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 (31) hide show
  1. package/README.md +3 -0
  2. package/dist/{chunk-SN3PN4G4.mjs → chunk-36AWDKPL.mjs} +6 -2
  3. package/dist/{chunk-CYWHPXNZ.mjs → chunk-7SH3COMQ.mjs} +1 -1
  4. package/dist/{chunk-BBTK6OX6.mjs → chunk-BECCGMU4.mjs} +383 -11
  5. package/dist/{chunk-4SZC5ZX3.mjs → chunk-CG75I3ZZ.mjs} +25477 -5374
  6. package/dist/{chunk-TPU42VH2.mjs → chunk-CMM4DOXC.mjs} +10 -5
  7. package/dist/{chunk-M3VZGHZQ.mjs → chunk-NNJYKR3O.mjs} +22 -11
  8. package/dist/{chunk-IGXGSDE3.mjs → chunk-OTKJZIOD.mjs} +1298 -384
  9. package/dist/{chunk-CO5KHIVX.mjs → chunk-TK5KEGNW.mjs} +1 -1
  10. package/dist/{chunk-FQUH2PU2.mjs → chunk-WVLBQDFP.mjs} +140 -7
  11. package/dist/{chunk-2OKZY3WN.mjs → chunk-YI3ZC2XI.mjs} +2 -2
  12. package/dist/{chunk-XZUUBMP3.mjs → chunk-YIM746WO.mjs} +1 -1
  13. package/dist/cli.js +140692 -118903
  14. package/dist/cli.mjs +362 -108
  15. package/dist/credential-mcp-server.js +1402 -350
  16. package/dist/credential-mcp-server.mjs +3 -3
  17. package/dist/discovery-mcp-server.js +1330 -401
  18. package/dist/discovery-mcp-server.mjs +4 -4
  19. package/dist/dist-2G7YTNH5.mjs +17 -0
  20. package/dist/inbox-mcp-server.js +1676 -629
  21. package/dist/inbox-mcp-server.mjs +3 -3
  22. package/dist/route-seed-worker.js +1244 -330
  23. package/dist/route-seed-worker.mjs +4 -4
  24. package/dist/run-write-guard-preload.js +1622 -332
  25. package/dist/run-write-guard-preload.mjs +4 -4
  26. package/dist/run-write-guard.js +1622 -332
  27. package/dist/run-write-guard.mjs +3 -3
  28. package/dist/spec-driver.js +1 -1
  29. package/dist/spec-driver.mjs +1 -1
  30. package/package.json +3 -2
  31. package/dist/dist-VTPBY2C4.mjs +0 -17
@@ -368,13 +368,13 @@ function isBlockedEnvVar(key) {
368
368
  function sanitizeTestVariables(vars) {
369
369
  if (!vars)
370
370
  return {};
371
- const clean2 = {};
371
+ const clean3 = {};
372
372
  for (const [key, value] of Object.entries(vars)) {
373
373
  if (!isBlockedEnvVar(key)) {
374
- clean2[key] = value;
374
+ clean3[key] = value;
375
375
  }
376
376
  }
377
- return clean2;
377
+ return clean3;
378
378
  }
379
379
  function buildSandboxedEnv(processEnv, testVariables) {
380
380
  const env = {};
@@ -1831,9 +1831,40 @@ function semanticTarget(label, platform, base2) {
1831
1831
  }
1832
1832
  };
1833
1833
  }
1834
+ function androidResourceOwners(value) {
1835
+ const owners = [];
1836
+ for (const match of value.matchAll(ANDROID_RESOURCE_ID_SUFFIX_RE)) {
1837
+ let runStart = match.index;
1838
+ while (runStart > 0 && /[a-z0-9_.]/i.test(value[runStart - 1]))
1839
+ runStart--;
1840
+ const owner = ownerAtEndOfRun(value.slice(runStart, match.index));
1841
+ if (owner !== null)
1842
+ owners.push(owner);
1843
+ }
1844
+ return owners;
1845
+ }
1846
+ function ownerAtEndOfRun(run) {
1847
+ const segments = run.split(".");
1848
+ const last = segments.length - 1;
1849
+ const startsWithLetter = (segment) => /^[a-z]/i.test(segment);
1850
+ if (last < 1 || !startsWithLetter(segments[last]))
1851
+ return null;
1852
+ let first = last;
1853
+ while (first > 0 && startsWithLetter(segments[first - 1]))
1854
+ first--;
1855
+ let offset = 0;
1856
+ for (let i = 0; i < first - 1; i++)
1857
+ offset += segments[i].length + 1;
1858
+ if (first > 0) {
1859
+ const letterAt = segments[first - 1].search(/[a-z]/i);
1860
+ if (letterAt >= 0)
1861
+ return run.slice(offset + letterAt);
1862
+ offset += segments[first - 1].length + 1;
1863
+ }
1864
+ return first < last ? run.slice(offset) : null;
1865
+ }
1834
1866
  function containsForeignAppPackage(value, targetAppId) {
1835
- for (const match of value.matchAll(ANDROID_RESOURCE_OWNER_RE)) {
1836
- const owner = match[1];
1867
+ for (const owner of androidResourceOwners(value)) {
1837
1868
  if (targetAppId && owner !== targetAppId && !owner.startsWith(`${targetAppId}.`))
1838
1869
  return true;
1839
1870
  }
@@ -1970,7 +2001,7 @@ function healMobileTestSteps(input) {
1970
2001
  summary: changed ? uniq(summary).slice(0, 12) : []
1971
2002
  };
1972
2003
  }
1973
- var MOBILE_STEP_ACTIONS, FOREIGN_PACKAGE_RE, ANDROID_RESOURCE_OWNER_RE, COMMON_EXTERNAL_PREFIXES;
2004
+ var MOBILE_STEP_ACTIONS, FOREIGN_PACKAGE_RE, ANDROID_RESOURCE_ID_SUFFIX_RE, COMMON_EXTERNAL_PREFIXES;
1974
2005
  var init_mobile_step_healer = __esm({
1975
2006
  "../shared/dist/mobile-step-healer.js"() {
1976
2007
  "use strict";
@@ -1991,7 +2022,7 @@ var init_mobile_step_healer = __esm({
1991
2022
  "assertStateDelta"
1992
2023
  ]);
1993
2024
  FOREIGN_PACKAGE_RE = /\b[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*){2,}\b/gi;
1994
- ANDROID_RESOURCE_OWNER_RE = /([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+):id\//gi;
2025
+ ANDROID_RESOURCE_ID_SUFFIX_RE = /:id\//gi;
1995
2026
  COMMON_EXTERNAL_PREFIXES = [
1996
2027
  "com.android.",
1997
2028
  "com.google.",
@@ -3701,7 +3732,7 @@ var init_assertion_classifier = __esm({
3701
3732
  // toHaveCount(0) stays tier 4 (absence proof) via TIER4_PATTERNS.
3702
3733
  [/\.toHaveCount\s*\(\s*[1-9]\d*\s*\)/, "non-zero toHaveCount asserts a record count that changes between runs"],
3703
3734
  // parenthesised counts like "(3)" or "[12]" in text - but not Playwright method args
3704
- [/(?<!\.\w{3,20}\s*)\(\s*\d+\s*\)(?!\s*[,;]?\s*\))/i, "parenthesised count is session-specific"],
3735
+ [/\((?<!\.\w{3,20}\s*\()\s*\d+\s*\)(?!\s*(?:[,;]\s*)?\))/i, "parenthesised count is session-specific"],
3705
3736
  // bare email addresses in assertion content
3706
3737
  [/\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b/i, "email address is user-specific"],
3707
3738
  // currency/balance amounts (dynamic account data)
@@ -3947,7 +3978,7 @@ var init_assertion_proof = __esm({
3947
3978
  PRESENCE_MATCHER = /\.(?:toBeVisible|toBeAttached|toBeEnabled|toBeInViewport|toBeEditable|toBeFocused)\s*\(|\.toHaveCount\s*\(\s*1\s*\)/;
3948
3979
  TEXT_MATCHER = /\.(?:toHaveText|toContainText)\s*\(/;
3949
3980
  ANY_VALUE_SOURCE = /^(?:\.|\\S|\\w|\\d|\[\^\\s\]|\[0-9\]|\[\\d\]|\[\\w\]|\[\\s\\S\])[+*]?\??$/;
3950
- ANY_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(\s*\/((?:\\.|[^/\\]){1,300})\/[a-z]*\s*\)/;
3981
+ ANY_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(\s*\/((?:\\.|[^/\\]){1,300})\/[a-z]*\s*[,)]/;
3951
3982
  EMPTY_NOT_VALUE = /\.not\s*\.\s*toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/;
3952
3983
  TEXT_MATCHER_LITERAL = /\.(?:toHaveText|toContainText)\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/;
3953
3984
  GET_BY_TEXT_LITERAL = /getByText\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/;
@@ -3964,7 +3995,7 @@ var init_assertion_proof = __esm({
3964
3995
  DATA_ROLE = /getByRole\s*\(\s*['"`](?:dialog|alertdialog|alert|cell|gridcell|row|rowheader|columnheader|listitem|article|option|status|note|tooltip|paragraph|term|definition|log|marquee|timer|meter)['"`]/gi;
3965
3996
  TEXT_LOCATOR = /\.getByText\s*\(|\.filter\s*\(\s*\{\s*hasText\s*:/g;
3966
3997
  CSS_LOCATOR = /\.locator\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/g;
3967
- SPECIFIC_CSS = /\[[^\]]*=|#[\w-]|has-text|:text/;
3998
+ SPECIFIC_CSS = /\[[^\][=]*=|#[\w-]|has-text|:text/;
3968
3999
  }
3969
4000
  });
3970
4001
 
@@ -4928,22 +4959,37 @@ function classifyAuthHandoffIntent(text) {
4928
4959
  function withoutIdpUrlAsserts(code) {
4929
4960
  return code.replace(/\.(?:toHaveURL|waitForURL)\s*\((?:[^)(]|\([^)]*\))*\)/g, (match) => /accountchooser|accounts\.google|appleid\.apple|login\.microsoftonline|\/v\d+\/signin|oauth/i.test(match) ? "" : match);
4930
4961
  }
4962
+ function stripLineComment(line) {
4963
+ let from = line.length;
4964
+ while (from > 0) {
4965
+ const c = line.charCodeAt(from - 1);
4966
+ if (c === 10 || c === 13 || c === 8232 || c === 8233)
4967
+ break;
4968
+ from--;
4969
+ }
4970
+ const at = line.indexOf("//", from);
4971
+ return at < 0 ? line : line.slice(0, at);
4972
+ }
4931
4973
  function stripComments(code) {
4932
- return replaceBlockComments(code, () => "").split("\n").map((line) => line.replace(/\/\/.*$/, "")).join("\n");
4974
+ return replaceBlockComments(code, () => "").split("\n").map(stripLineComment).join("\n");
4933
4975
  }
4934
- function balancedCall(code, start) {
4935
- const open = code.indexOf("(", start);
4936
- if (open < 0)
4937
- return null;
4938
- let depth = 0;
4939
- for (let i = open; i < code.length; i++) {
4976
+ function closingParens(code) {
4977
+ const close = new Int32Array(code.length).fill(-1);
4978
+ const open = [];
4979
+ for (let i = 0; i < code.length; i++) {
4940
4980
  const c = code[i];
4941
4981
  if (c === "(")
4942
- depth++;
4943
- else if (c === ")" && --depth === 0)
4944
- return { inner: code.slice(open + 1, i), end: i };
4982
+ open.push(i);
4983
+ else if (c === ")" && open.length > 0)
4984
+ close[open.pop()] = i;
4945
4985
  }
4946
- return null;
4986
+ return close;
4987
+ }
4988
+ function balancedCall(code, close, start) {
4989
+ const open = code.indexOf("(", start);
4990
+ if (open < 0 || close[open] < 0)
4991
+ return null;
4992
+ return { inner: code.slice(open + 1, close[open]), end: close[open] };
4947
4993
  }
4948
4994
  function resolveLocator(code, subject) {
4949
4995
  const id = subject.trim().match(/^([A-Za-z_$][\w$]*)$/);
@@ -4991,8 +5037,10 @@ function isAuthHandoffDismissCode(code) {
4991
5037
  const expects = [];
4992
5038
  const re = new RegExp(EXPECT_CALL_RE.source, "g");
4993
5039
  let match;
5040
+ let close = null;
4994
5041
  while (match = re.exec(body)) {
4995
- const found = balancedCall(body, match.index);
5042
+ close ??= closingParens(body);
5043
+ const found = balancedCall(body, close, match.index);
4996
5044
  if (found)
4997
5045
  expects.push({ inner: found.inner, tail: body.slice(found.end, found.end + 48) });
4998
5046
  }
@@ -5037,12 +5085,209 @@ var init_auth_handoff_coverage = __esm({
5037
5085
  }
5038
5086
  });
5039
5087
 
5088
+ // ../shared/dist/auth-plan.js
5089
+ function hasInlineLoginSteps(playwrightCode) {
5090
+ if (!playwrightCode)
5091
+ return false;
5092
+ return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
5093
+ }
5094
+ function hasGeneratedAuthGuard(playwrightCode) {
5095
+ if (!playwrightCode)
5096
+ return false;
5097
+ return /\/\/ >>> VALIDATE_QA_AUTH v(\d+)\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\1\b/.test(playwrightCode);
5098
+ }
5099
+ function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
5100
+ if (!sharedAuthInjectEnabled)
5101
+ return false;
5102
+ if (tags.includes("@auth"))
5103
+ return false;
5104
+ if (tags.includes("@api"))
5105
+ return true;
5106
+ if (!tags.includes("@needs-auth"))
5107
+ return false;
5108
+ if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
5109
+ return true;
5110
+ const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
5111
+ const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
5112
+ return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
5113
+ }
5114
+ function codeReadsStoredCredentials(code) {
5115
+ if (!code)
5116
+ return false;
5117
+ if (/__vqaLogin\b/.test(code))
5118
+ return true;
5119
+ for (const m of code.matchAll(/TEST_[A-Z0-9_]+/g)) {
5120
+ if (/_(?:EMAIL|PASSWORD)/.test(m[0]))
5121
+ return true;
5122
+ }
5123
+ for (const m of code.matchAll(/process\.env\s*\[([^\]]*)\]?/g)) {
5124
+ if (!/^\s*(['"])[^'"`]*\1\s*$/.test(m[1]))
5125
+ return true;
5126
+ }
5127
+ return false;
5128
+ }
5129
+ function explicitAuthTypeTags(t) {
5130
+ const found = /* @__PURE__ */ new Set();
5131
+ for (const tag of t) {
5132
+ if (AUTH_TYPE_TAGS.includes(tag))
5133
+ found.add(tag);
5134
+ else if (Object.hasOwn(AUTH_TAG_ALIASES, tag))
5135
+ found.add(AUTH_TAG_ALIASES[tag]);
5136
+ }
5137
+ return [...found];
5138
+ }
5139
+ function invalidPlan(configurationError) {
5140
+ return {
5141
+ version: 1,
5142
+ source: "invalid",
5143
+ acquisition: "unsupported",
5144
+ presentation: "none",
5145
+ needsCookieState: false,
5146
+ needsStandingHeaders: false,
5147
+ standingHeadersMode: "none",
5148
+ needsLoginCreds: false,
5149
+ requiredEnvKeys: [],
5150
+ configurationError
5151
+ };
5152
+ }
5153
+ function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
5154
+ return {
5155
+ version: 1,
5156
+ source: "explicit",
5157
+ authTypeTag: authTypeTag2,
5158
+ acquisition,
5159
+ presentation,
5160
+ needsCookieState: opts.cookie,
5161
+ needsStandingHeaders: opts.standingMode === "required",
5162
+ standingHeadersMode: opts.standingMode,
5163
+ needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
5164
+ requiredEnvKeys: opts.requiredEnvKeys
5165
+ };
5166
+ }
5167
+ function deriveAuthPlan(tags, code, opts) {
5168
+ const t = Array.isArray(tags) ? tags : [];
5169
+ const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
5170
+ const isApi = t.includes("@api");
5171
+ const explicit = isApi ? explicitAuthTypeTags(t) : [];
5172
+ if (explicit.length > 0) {
5173
+ if (t.includes("@auth")) {
5174
+ return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
5175
+ }
5176
+ const loginCompose = explicit.filter((e) => e === AUTH_TAG_COOKIE_STATE || e === AUTH_TAG_LOGIN_COOKIE || e === AUTH_TAG_LOGIN_BEARER);
5177
+ if (explicit.length === 2 && explicit.includes(AUTH_TAG_STANDING) && loginCompose.length === 1) {
5178
+ const composedKeys = [...TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY];
5179
+ switch (loginCompose[0]) {
5180
+ case AUTH_TAG_COOKIE_STATE:
5181
+ return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "required", requiredEnvKeys: composedKeys });
5182
+ case AUTH_TAG_LOGIN_COOKIE:
5183
+ return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
5184
+ case AUTH_TAG_LOGIN_BEARER:
5185
+ return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
5186
+ }
5187
+ }
5188
+ if (explicit.length > 1) {
5189
+ return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
5190
+ }
5191
+ switch (explicit[0]) {
5192
+ case AUTH_TAG_UNSUPPORTED:
5193
+ return {
5194
+ ...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
5195
+ };
5196
+ case AUTH_TAG_NONE:
5197
+ return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
5198
+ case AUTH_TAG_STANDING:
5199
+ return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
5200
+ case AUTH_TAG_LOGIN_BEARER:
5201
+ return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
5202
+ case AUTH_TAG_LOGIN_COOKIE:
5203
+ return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
5204
+ case AUTH_TAG_COOKIE_STATE:
5205
+ return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
5206
+ }
5207
+ }
5208
+ const cookie = legacyNeedsCookieState(t, code, sharedFlag);
5209
+ const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
5210
+ const selfLoginWithoutStoredCreds = t.includes("@auth") && !t.includes("@needs-auth") && !isApi && !!code?.trim() && !/TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin\b/.test(code);
5211
+ const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
5212
+ const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
5213
+ return {
5214
+ version: 1,
5215
+ source: "legacy",
5216
+ acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
5217
+ presentation: authedLegacy ? "cookie" : "none",
5218
+ needsCookieState: cookie,
5219
+ needsStandingHeaders: false,
5220
+ // A legacy bare-@api test keeps injecting standing headers IF the project configured them
5221
+ // (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
5222
+ standingHeadersMode: isApi ? "legacy-if-configured" : "none",
5223
+ needsLoginCreds,
5224
+ requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
5225
+ };
5226
+ }
5227
+ function authTypeTag(tags) {
5228
+ const t = Array.isArray(tags) ? tags : [];
5229
+ const explicit = explicitAuthTypeTags(t);
5230
+ return explicit.length === 1 ? explicit[0] : void 0;
5231
+ }
5232
+ var AUTH_TAG_COOKIE_STATE, AUTH_TAG_LOGIN_COOKIE, AUTH_TAG_LOGIN_BEARER, AUTH_TAG_STANDING, AUTH_TAG_NONE, AUTH_TAG_UNSUPPORTED, AUTH_TYPE_TAGS, AUTH_TAG_ALIASES, TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY;
5233
+ var init_auth_plan = __esm({
5234
+ "../shared/dist/auth-plan.js"() {
5235
+ "use strict";
5236
+ AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
5237
+ AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
5238
+ AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
5239
+ AUTH_TAG_STANDING = "@auth-standing";
5240
+ AUTH_TAG_NONE = "@auth-none";
5241
+ AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
5242
+ AUTH_TYPE_TAGS = [
5243
+ AUTH_TAG_COOKIE_STATE,
5244
+ AUTH_TAG_LOGIN_COOKIE,
5245
+ AUTH_TAG_LOGIN_BEARER,
5246
+ AUTH_TAG_STANDING,
5247
+ AUTH_TAG_NONE,
5248
+ AUTH_TAG_UNSUPPORTED
5249
+ ];
5250
+ AUTH_TAG_ALIASES = {
5251
+ "@auth-cookie": AUTH_TAG_COOKIE_STATE,
5252
+ "@auth-bearer": AUTH_TAG_LOGIN_BEARER,
5253
+ "@auth-token": AUTH_TAG_LOGIN_BEARER,
5254
+ "@auth-api-key": AUTH_TAG_STANDING,
5255
+ "@auth-standing-header": AUTH_TAG_STANDING
5256
+ };
5257
+ TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
5258
+ API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
5259
+ }
5260
+ });
5261
+
5040
5262
  // ../shared/dist/assertion-strength.js
5263
+ function parseLastActionProofMode(raw) {
5264
+ const v = (raw ?? "").trim().toLowerCase();
5265
+ return v === "on" || v === "shadow" ? v : "off";
5266
+ }
5267
+ function isUrlProof(stmt) {
5268
+ if (URL_PROOF.test(stmt))
5269
+ return true;
5270
+ let calls = 0;
5271
+ for (const open of stmt.matchAll(POLL_OPEN)) {
5272
+ if (++calls > POLL_CALLS_MAX)
5273
+ break;
5274
+ const from = open.index + open[0].length;
5275
+ const args = balancedArguments(stmt, from, MATCHER_ARGS_MAX);
5276
+ if (args === null)
5277
+ continue;
5278
+ const subject = pollSubject(args);
5279
+ if (!POLL_PAGE_URL.test(subject))
5280
+ continue;
5281
+ if (!POLL_PAGE_URL_ONLY.test(subject) || PAGE_VALUE_MATCHER.test(stmt.slice(from + args.length)))
5282
+ return true;
5283
+ }
5284
+ return false;
5285
+ }
5041
5286
  function liveExpectStatements(uncommented) {
5042
5287
  const masked = maskedOutsideStrings(uncommented).split("\n");
5043
5288
  return accumulateStatements(uncommented.split("\n")).filter((s) => EXPECT_STMT.test(s.stmt) && EXPECT_STMT.test(masked[s.startLine - 1] ?? ""));
5044
5289
  }
5045
- function stripLineComment(ln) {
5290
+ function stripLineComment2(ln) {
5046
5291
  let quote = null;
5047
5292
  for (let i = 0; i < ln.length; i++) {
5048
5293
  const c = ln[i];
@@ -5065,7 +5310,7 @@ function splitInteractionStatementsWithLines(lines) {
5065
5310
  let depth = 0;
5066
5311
  let scaffold = false;
5067
5312
  for (let i = 0; i < lines.length; i++) {
5068
- const ln = stripLineComment(lines[i]).trim();
5313
+ const ln = stripLineComment2(lines[i]).trim();
5069
5314
  if (!ln)
5070
5315
  continue;
5071
5316
  const isContinuation = out.length > 0 && (ln.startsWith(".") || depth > 0 && !scaffold);
@@ -5089,7 +5334,7 @@ function testBlockIds(lines) {
5089
5334
  let any = false;
5090
5335
  let block = 0;
5091
5336
  for (let i = 0; i < lines.length; i++) {
5092
- const ln = stripLineComment(lines[i]).trim();
5337
+ const ln = stripLineComment2(lines[i]).trim();
5093
5338
  if (!open) {
5094
5339
  if (!TEST_BLOCK_OPEN.test(ln))
5095
5340
  continue;
@@ -5173,7 +5418,7 @@ function expectSubjectSpan(stmt) {
5173
5418
  }
5174
5419
  return null;
5175
5420
  }
5176
- function resolveSubjectBindings(stmts, bindings, blockOfLine) {
5421
+ function resolveSubjectBindings(stmts, bindings, blockOfLine, boundAt) {
5177
5422
  if (bindings.length === 0)
5178
5423
  return [...stmts];
5179
5424
  return stmts.map((item) => {
@@ -5196,14 +5441,22 @@ function resolveSubjectBindings(stmts, bindings, blockOfLine) {
5196
5441
  subject = `${lead[1]}${found.expr.replace(/;\s*$/, "")}${subject.slice(lead[0].length)}`;
5197
5442
  line = found.line;
5198
5443
  }
5199
- return { ...item, stmt: `${item.stmt.slice(0, span.from)}${subject}${item.stmt.slice(span.to)}` };
5444
+ const out = { ...item, stmt: `${item.stmt.slice(0, span.from)}${subject}${item.stmt.slice(span.to)}` };
5445
+ if (boundAt !== void 0 && line !== item.startLine)
5446
+ boundAt.set(out, line);
5447
+ return out;
5200
5448
  });
5201
5449
  }
5202
5450
  function stripLoginScaffolding(code) {
5203
- const out = [];
5451
+ const lines = code.split("\n");
5452
+ return keptScaffoldingLines(lines).map((i) => lines[i]).join("\n");
5453
+ }
5454
+ function keptScaffoldingLines(lines) {
5455
+ const kept = [];
5204
5456
  let inAuth = false;
5205
5457
  let inInbox = false;
5206
- for (const ln of code.split("\n")) {
5458
+ for (let i = 0; i < lines.length; i++) {
5459
+ const ln = lines[i];
5207
5460
  if (!inAuth && !inInbox && AUTH_START.test(ln)) {
5208
5461
  inAuth = true;
5209
5462
  continue;
@@ -5224,9 +5477,9 @@ function stripLoginScaffolding(code) {
5224
5477
  }
5225
5478
  if (LOGIN_CALL_LINE.test(ln))
5226
5479
  continue;
5227
- out.push(ln);
5480
+ kept.push(i);
5228
5481
  }
5229
- return out.join("\n");
5482
+ return kept;
5230
5483
  }
5231
5484
  function stripCommentsAndLogs(code) {
5232
5485
  return stripCoverageComments(code).split("\n").filter((ln) => !/^\s*console\./.test(ln)).join("\n");
@@ -5378,6 +5631,41 @@ function isVacuousErrorAbsence(stmt, presence) {
5378
5631
  return false;
5379
5632
  return !presence().includes(m[1]);
5380
5633
  }
5634
+ function isVacuousAccountAbsence(stmt, shown, account) {
5635
+ if (!claimsAbsence(stmt))
5636
+ return false;
5637
+ if (!ABSENCE_MATCHER.test(stmt)) {
5638
+ const negated = NEGATED_TEXT.exec(stmt);
5639
+ if (!negated)
5640
+ return false;
5641
+ const read = account.exec(stmt.slice(negated.index));
5642
+ return read !== null && !shown().has(read[0]);
5643
+ }
5644
+ const m = account.exec(stmt);
5645
+ if (!m || shown().has(m[0]))
5646
+ return false;
5647
+ for (let at = endOfEnclosingCall(stmt, m.index + m[0].length); at < stmt.length; at = endOfEnclosingCall(stmt, at)) {
5648
+ SCOPING_STEP.lastIndex = at;
5649
+ if (SCOPING_STEP.test(stmt))
5650
+ return false;
5651
+ }
5652
+ return true;
5653
+ }
5654
+ function endOfEnclosingCall(stmt, from) {
5655
+ let depth = 0;
5656
+ for (let i = from; i < stmt.length; i++) {
5657
+ const c = stmt[i];
5658
+ if (c === "'" || c === '"') {
5659
+ for (i++; i < stmt.length && stmt[i] !== c; i++)
5660
+ if (stmt[i] === "\\")
5661
+ i++;
5662
+ } else if (c === "(")
5663
+ depth++;
5664
+ else if (c === ")" && depth-- === 0)
5665
+ return i + 1;
5666
+ }
5667
+ return stmt.length;
5668
+ }
5381
5669
  function isBareContainerVisibility(stmt) {
5382
5670
  return BARE_CONTAINER_ROLE.test(stmt) && !GET_BY_TEXT.test(stmt) && !HAS_TEXT_FILTER.test(stmt) && !TEXT_CONTENT_MATCHER.test(stmt);
5383
5671
  }
@@ -5459,7 +5747,7 @@ function roleNames(s) {
5459
5747
  function interactedControlNames(lines) {
5460
5748
  const names = /* @__PURE__ */ new Set();
5461
5749
  for (const raw of lines) {
5462
- const ln = stripLineComment(raw);
5750
+ const ln = stripLineComment2(raw);
5463
5751
  if (!INTERACT_VERB.test(ln))
5464
5752
  continue;
5465
5753
  for (const n of roleNames(ln))
@@ -5655,6 +5943,14 @@ function balancedArguments(code, from, limit = code.length) {
5655
5943
  }
5656
5944
  return null;
5657
5945
  }
5946
+ function expectCallTail(stmt) {
5947
+ const at = stmt.search(EXPECT_CALL_RE);
5948
+ if (at < 0)
5949
+ return null;
5950
+ const from = stmt.indexOf("(", at) + 1;
5951
+ const args = balancedArguments(stmt, from, MATCHER_ARGS_MAX);
5952
+ return args === null ? null : stmt.slice(from + args.length).trimStart();
5953
+ }
5658
5954
  function matcherArgumentsAreConstant(body, from) {
5659
5955
  MATCHER_OPEN.lastIndex = from;
5660
5956
  const opened = MATCHER_OPEN.exec(body);
@@ -5696,9 +5992,12 @@ function envValueKey(name) {
5696
5992
  function enteredValues(stmts) {
5697
5993
  const values = /* @__PURE__ */ new Map();
5698
5994
  const navLines = [];
5995
+ const accountConsts = /* @__PURE__ */ new Set();
5699
5996
  for (const { stmt, startLine } of stmts) {
5700
5997
  if (PAGE_NAV.test(stmt))
5701
5998
  navLines.push(startLine);
5999
+ for (const c of stmt.matchAll(ACCOUNT_CONST))
6000
+ accountConsts.add(c[1].replace(/\$/g, "\\$"));
5702
6001
  let m;
5703
6002
  ENTERED_VALUE.lastIndex = 0;
5704
6003
  const add = (value) => {
@@ -5714,7 +6013,8 @@ function enteredValues(stmts) {
5714
6013
  while (m = ENTERED_ENV.exec(stmt))
5715
6014
  add(envValueKey(m[1] ?? m[2] ?? m[3] ?? m[4]));
5716
6015
  }
5717
- return { values, navLines };
6016
+ const consts = accountConsts.size > 0 ? `|(?<![\\w$.])(?:${[...accountConsts].join("|")})(?![\\w$])` : "";
6017
+ return { values, navLines, account: new RegExp(`${ACCOUNT_ENV}${consts}`) };
5718
6018
  }
5719
6019
  function countBelow(lines, line) {
5720
6020
  let lo = 0;
@@ -5851,6 +6151,9 @@ function comparesWithItself(subject, tail) {
5851
6151
  return expected === squash2(m[1] === "toHaveCount" ? `${subject}.count()` : subject);
5852
6152
  }
5853
6153
  function asPlainAssertion(s) {
6154
+ return plainAssertion(s, false);
6155
+ }
6156
+ function plainAssertion(s, keepSelfCompare) {
5854
6157
  const { stmt } = s;
5855
6158
  if (COUNT_AT_LEAST_ZERO.test(stmt))
5856
6159
  return [];
@@ -5859,13 +6162,13 @@ function asPlainAssertion(s) {
5859
6162
  return [s];
5860
6163
  const plain = subject.replace(/^await\s+/, "").trim();
5861
6164
  const subjectAt = stmt.indexOf(subject, stmt.search(EXPECT_CALL_RE));
5862
- const tail = stmt.slice(subjectAt + subject.length).trimStart();
6165
+ const tail = (stmt.startsWith("expect.poll", stmt.search(EXPECT_CALL_RE)) ? expectCallTail(stmt) : null) ?? stmt.slice(subjectAt + subject.length).trimStart();
5863
6166
  const as = (rewritten) => [{ ...s, stmt: rewritten }];
5864
6167
  if (LOCATOR_EXPR.test(plain) && !LOCATOR_VALUE_READ.test(plain) && ALWAYS_TRUE_ON_OBJECT.test(tail))
5865
6168
  return [];
5866
6169
  if (DEFINED_VALUE_READ.test(plain) && ALWAYS_DEFINED.test(tail))
5867
6170
  return [];
5868
- if (comparesWithItself(plain, tail))
6171
+ if (!keepSelfCompare && comparesWithItself(plain, tail))
5869
6172
  return [];
5870
6173
  const visible = /^(.+)\.\s*isVisible\s*\(\s*\)$/s.exec(plain);
5871
6174
  if (visible && IS_VISIBLE_TRUE.test(tail))
@@ -5876,6 +6179,9 @@ function asPlainAssertion(s) {
5876
6179
  }
5877
6180
  return [s];
5878
6181
  }
6182
+ function assertionStrengthHoistedReadsOn(value = typeof process !== "undefined" ? process.env?.ASSERTION_STRENGTH_HOISTED_READS : void 0) {
6183
+ return /^(1|true|on)$/i.test((value ?? "").trim());
6184
+ }
5879
6185
  function creditsOutcomeAlways(stmt, entered, startLine) {
5880
6186
  if (!OUTCOME_ALWAYS.some((re) => re.test(stmt)))
5881
6187
  return false;
@@ -5908,6 +6214,22 @@ function successNoticeQuote(stmt) {
5908
6214
  }
5909
6215
  return null;
5910
6216
  }
6217
+ function namedPagePath(arg) {
6218
+ const text = arg.trim();
6219
+ let path;
6220
+ const quoted = /^(['"`])([^'"`]*)\1$/.exec(text);
6221
+ const regex = quoted ? null : /^\/((?:\\.|[^/\\])+)\/[a-z]*$/.exec(text);
6222
+ if (quoted) {
6223
+ path = quoted[2].replace(/^\$\{[^}]*\}/, "").replace(/^\*\*/, "").replace(/^[a-z][a-z\d+.-]*:\/\/[^/]*/i, "").replace(/\/\*\*$/, "/");
6224
+ } else if (regex) {
6225
+ path = regex[1].replace(/^\^/, "").replace(/^\.\*/, "").replace(/\$$/, "").replace(/\\([/.-])/g, "$1");
6226
+ } else {
6227
+ return null;
6228
+ }
6229
+ if (!PLAIN_PAGE_PATH.test(path))
6230
+ return null;
6231
+ return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
6232
+ }
5911
6233
  function provesStoredControlValue(stmt, startLine, persistence) {
5912
6234
  if (!persistence || startLine === void 0)
5913
6235
  return false;
@@ -5934,7 +6256,64 @@ function reopenedAfterSave(startLine, persistence, reopens = persistence.reloadL
5934
6256
  }
5935
6257
  return lo < reopens.length && reopens[lo] < startLine;
5936
6258
  }
5937
- function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine, persistence) {
6259
+ function actsBetween(withLines, from, to) {
6260
+ let acts = actLinesOf.get(withLines);
6261
+ if (acts === void 0) {
6262
+ acts = withLines.filter((s) => ACTS.test(s.stmt)).map((s) => s.startLine).sort((a, b) => a - b);
6263
+ actLinesOf.set(withLines, acts);
6264
+ }
6265
+ let lo = 0;
6266
+ let hi = acts.length;
6267
+ while (lo < hi) {
6268
+ const mid = lo + hi >> 1;
6269
+ if (acts[mid] <= from)
6270
+ lo = mid + 1;
6271
+ else
6272
+ hi = mid;
6273
+ }
6274
+ return lo < acts.length && acts[lo] < to;
6275
+ }
6276
+ function signInProofWindow(stmts, withLines, lines, blockIds) {
6277
+ if (blockIds === null)
6278
+ return void 0;
6279
+ const testBodies2 = /* @__PURE__ */ new Set();
6280
+ for (let i = 0; i < lines.length; i++) {
6281
+ const id = blockIds[i];
6282
+ if (id > 0 && (i === 0 || blockIds[i - 1] !== id) && TEST_BODY_OPEN.test(stripLineComment2(lines[i]).trim()))
6283
+ testBodies2.add(id);
6284
+ }
6285
+ const blockOf = (i) => blockIds[withLines[i].startLine - 1];
6286
+ const passwordAt = stmts.findIndex((stmt, i) => testBodies2.has(blockOf(i)) && ENV_PASSWORD_ENTRY.test(stmt));
6287
+ if (passwordAt < 0)
6288
+ return void 0;
6289
+ const body = blockOf(passwordAt);
6290
+ let clickAt = passwordAt + 1;
6291
+ while (clickAt < stmts.length && FORM_INPUT.test(stmts[clickAt]) && !ANY_CLICK.test(stmts[clickAt]))
6292
+ clickAt++;
6293
+ if (clickAt >= stmts.length || blockOf(clickAt) !== body || !ANY_CLICK.test(stmts[clickAt]) || NAV_LOCATOR.test(stmts[clickAt]))
6294
+ return void 0;
6295
+ let end = clickAt + 1;
6296
+ for (; end < stmts.length && blockOf(end) === body; end++) {
6297
+ if (CLOSERS_ONLY.test(stmts[end]))
6298
+ continue;
6299
+ if (!ASSERT_OR_WAIT.test(stmts[end]) || ACTS.test(stmts[end]))
6300
+ return void 0;
6301
+ }
6302
+ return { after: withLines[clickAt].startLine, until: end < stmts.length ? withLines[end].startLine : Number.POSITIVE_INFINITY };
6303
+ }
6304
+ function isAccountControlCheck(stmt) {
6305
+ if (!VISIBILITY_MATCHER.test(stmt) || NEGATED_ASSERT.test(stmt) || OPTION_ABSENCE.test(stmt))
6306
+ return false;
6307
+ const role = ACCOUNT_CONTROL_ROLE.exec(stmt);
6308
+ if (!role)
6309
+ return false;
6310
+ const rest = stmt.slice(role.index + role[0].length, role.index + role[0].length + 300);
6311
+ const close = rest.indexOf("}");
6312
+ const options = close < 0 ? rest : rest.slice(0, close);
6313
+ const name = options.match(/(?:^|[\s,])name\s*:\s*/);
6314
+ return name !== null && ACCOUNT_EMAIL_ENV.test(options.slice(name.index + name[0].length));
6315
+ }
6316
+ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine, persistence, signedIn) {
5938
6317
  if (mutated && hasAttributeTransition(stmts))
5939
6318
  return { kind: "durable" };
5940
6319
  const notices = [];
@@ -5944,6 +6323,13 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
5944
6323
  };
5945
6324
  let presenceText;
5946
6325
  const presence = () => presenceText ??= stmts.filter((o) => !ABSENCE_MATCHER.test(o.stmt) && !NEGATED_ASSERT.test(o.stmt)).map((o) => o.stmt).join("\n");
6326
+ let shownAccounts;
6327
+ const shown = () => {
6328
+ if (shownAccounts)
6329
+ return shownAccounts;
6330
+ const ref = new RegExp(entered.account.source, "g");
6331
+ return shownAccounts = new Set(stmts.filter((o) => !claimsAbsence(o.stmt)).flatMap((o) => Array.from(o.stmt.matchAll(ref), (m) => m[0])));
6332
+ };
5947
6333
  for (const { stmt, startLine } of stmts) {
5948
6334
  if (firstMutLine !== void 0 && startLine !== void 0 && startLine < firstMutLine)
5949
6335
  continue;
@@ -5951,6 +6337,11 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
5951
6337
  continue;
5952
6338
  if (COUNT_ONE.test(stmt) && quotedAssertedStrings(stmt).some((t) => clicked.has(t.toLowerCase())))
5953
6339
  continue;
6340
+ if (isVacuousAccountAbsence(stmt, shown, entered.account))
6341
+ continue;
6342
+ if (signedIn && startLine !== void 0 && startLine > signedIn.after && startLine < signedIn.until && isAccountControlCheck(stmt)) {
6343
+ return { kind: "durable" };
6344
+ }
5954
6345
  if (creditsOutcomeAlways(stmt, entered, startLine))
5955
6346
  return { kind: "durable" };
5956
6347
  if (mutated && !NEGATED_ASSERT.test(stmt) && MESSAGE_SURFACE.test(stmt) && OUTCOME_TEXT.test(assertedText(stmt))) {
@@ -6032,6 +6423,53 @@ function noticeOnlyExemption(clicked, noticeText, interactionStmts) {
6032
6423
  function detectOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine) {
6033
6424
  return classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine).kind !== "none";
6034
6425
  }
6426
+ function clickLabel(stmt) {
6427
+ let label = null;
6428
+ for (const m of stmt.matchAll(CONTROL_LABEL))
6429
+ label = m[2] ?? m[3] ?? null;
6430
+ return label;
6431
+ }
6432
+ function teardownLines(lines) {
6433
+ const out = new Array(lines.length).fill(false);
6434
+ let open = false;
6435
+ let depth = 0;
6436
+ for (let i = 0; i < lines.length; i++) {
6437
+ const ln = stripLineComment2(lines[i]).trim();
6438
+ let from = 0;
6439
+ if (!open) {
6440
+ const m = TEARDOWN_OPEN.exec(ln);
6441
+ if (!m)
6442
+ continue;
6443
+ open = true;
6444
+ depth = 0;
6445
+ from = m.index;
6446
+ }
6447
+ out[i] = true;
6448
+ let closed = false;
6449
+ let quote = null;
6450
+ for (let k = from; k < ln.length; k++) {
6451
+ const ch = ln[k];
6452
+ if (quote !== null) {
6453
+ if (ch === "\\")
6454
+ k++;
6455
+ else if (ch === quote)
6456
+ quote = null;
6457
+ continue;
6458
+ }
6459
+ if (ch === "'" || ch === '"' || ch === "`")
6460
+ quote = ch;
6461
+ else if (ch === "{")
6462
+ depth++;
6463
+ else if (ch === "}") {
6464
+ depth--;
6465
+ closed = true;
6466
+ }
6467
+ }
6468
+ if (depth <= 0 && closed)
6469
+ open = false;
6470
+ }
6471
+ return out;
6472
+ }
6035
6473
  function validateAssertionStrength(code, opts = {}) {
6036
6474
  const body = stripLoginScaffolding(code);
6037
6475
  const lines = body.split("\n");
@@ -6075,7 +6513,13 @@ function validateAssertionStrength(code, opts = {}) {
6075
6513
  const interactionStmts = inlineLocatorBindings(interactionStmtsRaw);
6076
6514
  const blockIds = testBlockIds(lines);
6077
6515
  const blockOfLine = (line) => blockIds?.[line - 1] ?? 0;
6078
- const checks = resolveSubjectBindings(stmts, scopedLocatorBindings(interactionStmtsWithLines, blockOfLine), blockOfLine);
6516
+ const hoistedReads = assertionStrengthHoistedReadsOn();
6517
+ const boundAt = hoistedReads ? /* @__PURE__ */ new Map() : void 0;
6518
+ const resolved = resolveSubjectBindings(stmts, scopedLocatorBindings(interactionStmtsWithLines, blockOfLine), blockOfLine, boundAt);
6519
+ const checks = boundAt === void 0 ? resolved : resolved.flatMap((s) => {
6520
+ const from = boundAt.get(s);
6521
+ return plainAssertion(s, from !== void 0 && actsBetween(interactionStmtsWithLines, from, s.startLine));
6522
+ });
6079
6523
  const insideTest = linesInsideTestBlocks(lines);
6080
6524
  const inTestOrder = (i) => insideTest === null || insideTest[interactionStmtsWithLines[i].startLine - 1] === true;
6081
6525
  let mutated = opts.interactionPerformed === true;
@@ -6085,9 +6529,34 @@ function validateAssertionStrength(code, opts = {}) {
6085
6529
  let navClicks = 0;
6086
6530
  const persistence = { mutLines: [], reloadLines: [], reloadCalls: [] };
6087
6531
  const openedPages = /* @__PURE__ */ new Set();
6532
+ let checkedPage = null;
6533
+ const savedOnCheckedPage = /* @__PURE__ */ new Set();
6534
+ const saveAt = (i, at) => {
6535
+ lastMutIdx = i;
6536
+ if (!inTestOrder(i))
6537
+ return;
6538
+ persistence.mutLines.push(at);
6539
+ if (firstMutIdx < 0)
6540
+ firstMutIdx = i;
6541
+ if (checkedPage !== null)
6542
+ savedOnCheckedPage.add(checkedPage);
6543
+ };
6544
+ const lastActionProof = opts.lastActionProof ?? "off";
6545
+ const teardown = lastActionProof === "off" ? null : teardownLines(lines);
6546
+ const acts = { last: null, prev: null, count: 0 };
6547
+ const noteAct = (i, at, kind) => {
6548
+ if (teardown === null || teardown[at - 1] || !inTestOrder(i))
6549
+ return;
6550
+ if (kind !== "enter")
6551
+ acts.count++;
6552
+ acts.prev = acts.last;
6553
+ acts.last = { i, kind };
6554
+ };
6088
6555
  for (let i = 0; i < interactionStmts.length; i++) {
6089
6556
  const ln = interactionStmts[i];
6090
6557
  const at = interactionStmtsWithLines[i].startLine;
6558
+ if (teardown !== null && acts.last?.kind === "fill" && ENTER_PRESS.test(ln))
6559
+ noteAct(i, at, "enter");
6091
6560
  if (RELOAD.test(ln)) {
6092
6561
  persistence.reloadLines.push(at);
6093
6562
  persistence.reloadCalls.push(at);
@@ -6095,32 +6564,31 @@ function validateAssertionStrength(code, opts = {}) {
6095
6564
  const opened = ln.match(/\.goto\s*\(\s*([^,)]+)/);
6096
6565
  if (opened) {
6097
6566
  const page = opened[1].trim();
6098
- if (openedPages.has(page))
6567
+ const path = namedPagePath(page);
6568
+ if (openedPages.has(page) || path !== null && savedOnCheckedPage.has(path))
6099
6569
  persistence.reloadLines.push(at);
6100
- else
6101
- openedPages.add(page);
6102
- }
6570
+ openedPages.add(page);
6571
+ checkedPage = null;
6572
+ }
6573
+ if (/\bpage\s*\.\s*go(?:Back|Forward)\s*\(/.test(ln))
6574
+ checkedPage = null;
6575
+ const checked = URL_CHECK_ARG.exec(ln);
6576
+ if (checked)
6577
+ checkedPage = namedPagePath(checked[1] ?? checked[2]);
6103
6578
  if (FORM_INPUT.test(ln)) {
6104
6579
  mutated = true;
6105
- lastMutIdx = i;
6106
- if (inTestOrder(i)) {
6107
- persistence.mutLines.push(at);
6108
- if (firstMutIdx < 0)
6109
- firstMutIdx = i;
6110
- }
6580
+ saveAt(i, at);
6581
+ noteAct(i, at, "fill");
6111
6582
  continue;
6112
6583
  }
6113
6584
  if (ANY_CLICK.test(ln)) {
6114
6585
  if (NAV_LOCATOR.test(ln)) {
6115
6586
  navClicks++;
6587
+ checkedPage = null;
6116
6588
  } else {
6117
6589
  mutated = true;
6118
- lastMutIdx = i;
6119
- if (inTestOrder(i)) {
6120
- persistence.mutLines.push(at);
6121
- if (firstMutIdx < 0)
6122
- firstMutIdx = i;
6123
- }
6590
+ saveAt(i, at);
6591
+ noteAct(i, at, "click");
6124
6592
  }
6125
6593
  }
6126
6594
  }
@@ -6153,9 +6621,28 @@ function validateAssertionStrength(code, opts = {}) {
6153
6621
  };
6154
6622
  }
6155
6623
  const orderedPersistence = firstMutLine !== void 0 ? persistence : void 0;
6156
- const proof = classifyOutcomeProof(checks, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence);
6624
+ const signedIn = signInProofWindow(interactionStmts, interactionStmtsWithLines, lines, blockIds);
6625
+ const proof = classifyOutcomeProof(checks, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence, signedIn);
6157
6626
  if (proof.kind === "durable") {
6158
6627
  const signals2 = base2({ interacted, mutated, navOnly, hasOutcomeProof: true, terminal });
6628
+ const { last, prev } = acts;
6629
+ if (teardown !== null && last !== null && acts.count >= 2 && last.kind !== "fill" && (last.kind === "enter" || isCommitClick(interactionStmts[last.i]) && !(prev?.kind === "click" && isDestructiveClick(interactionStmts[prev.i])))) {
6630
+ const lastLine = interactionStmtsWithLines[last.i].startLine;
6631
+ const clicked = clickedControlNames(interactionStmts);
6632
+ const after = classifyOutcomeProof(checks, mutated, reloaded, clicked, entered, lastLine, orderedPersistence, signedIn);
6633
+ const unproven = after.kind === "none" || after.kind === "notice" && noticeOnlyExemption(clicked, after.noticeText, interactionStmts) === null;
6634
+ if (unproven && lastActionProof === "on") {
6635
+ const line = (keptScaffoldingLines(code.split("\n"))[lastLine - 1] ?? lastLine - 1) + 1;
6636
+ return {
6637
+ ok: false,
6638
+ severity: "block",
6639
+ reason: `the last save (line ${line}) has no outcome check after it (the earlier check proves only the earlier action)`,
6640
+ signals: { ...signals2, lastActionUnproven: true }
6641
+ };
6642
+ }
6643
+ if (unproven)
6644
+ signals2.lastActionUnproven = true;
6645
+ }
6159
6646
  return { ok: true, severity: "none", reason: "proves an outcome (count / diff / value / revealed content)", signals: signals2 };
6160
6647
  }
6161
6648
  if (mutated && proof.kind === "notice") {
@@ -6204,7 +6691,7 @@ function validateAssertionStrength(code, opts = {}) {
6204
6691
  signals
6205
6692
  };
6206
6693
  }
6207
- const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && URL_PROOF.test(s));
6694
+ const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && isUrlProof(s));
6208
6695
  if (urlProofAfter) {
6209
6696
  return {
6210
6697
  ok: false,
@@ -6241,7 +6728,7 @@ function validateAssertionStrength(code, opts = {}) {
6241
6728
  signals
6242
6729
  };
6243
6730
  }
6244
- var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, NEGATED_ASSERT_NOT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, VALUE_ABSENCE, OPTION_ABSENCE, NOT_BEFORE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, NEGATED_TEXT, ASYNC_DESTRUCTURE_OPEN, REQUEST_WORD, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, ENV_READ, ENTERED_ENV, HAVE_VALUE_ENV, IN_FLIGHT_TEXT, LOCATOR_VALUE_READ, LOCATOR_EXPR, ALWAYS_TRUE_ON_OBJECT, COUNT_AT_LEAST_ZERO, DEFINED_VALUE_READ, ALWAYS_DEFINED, SELF_COMPARE_MATCHER, IS_VISIBLE_TRUE, PAGE_VALUE_MATCHER, COUNT_ONE, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD;
6731
+ var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, POLL_OPEN, POLL_PAGE_URL, POLL_PAGE_URL_ONLY, POLL_CALLS_MAX, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, NEGATED_ASSERT_NOT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, VALUE_ABSENCE, OPTION_ABSENCE, NOT_BEFORE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, SCOPING_STEP, NEGATED_TEXT, ASYNC_DESTRUCTURE_OPEN, REQUEST_WORD, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, ENV_READ, ENTERED_ENV, HAVE_VALUE_ENV, ACCOUNT_KEYS, ACCOUNT_ENV, ACCOUNT_CONST, IN_FLIGHT_TEXT, LOCATOR_VALUE_READ, LOCATOR_EXPR, ALWAYS_TRUE_ON_OBJECT, COUNT_AT_LEAST_ZERO, DEFINED_VALUE_READ, ALWAYS_DEFINED, SELF_COMPARE_MATCHER, IS_VISIBLE_TRUE, PAGE_VALUE_MATCHER, COUNT_ONE, URL_CHECK_ARG, PLAIN_PAGE_PATH, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, ACCOUNT_EMAIL_ENV, ENV_PASSWORD_ENTRY, ACCOUNT_CONTROL_ROLE, ASSERT_OR_WAIT, ACTS, actLinesOf, CLOSERS_ONLY, TEST_BODY_OPEN, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD, COMMIT_CONTROL, NOT_COMMIT_CONTROL, DESTRUCTIVE_CONTROL, ENTER_PRESS, CONTROL_LABEL, isCommitClick, isDestructiveClick, TEARDOWN_OPEN;
6245
6732
  var init_assertion_strength = __esm({
6246
6733
  "../shared/dist/assertion-strength.js"() {
6247
6734
  "use strict";
@@ -6250,6 +6737,7 @@ var init_assertion_strength = __esm({
6250
6737
  init_assertion_proof();
6251
6738
  init_auth_handoff_coverage();
6252
6739
  init_expect_call();
6740
+ init_auth_plan();
6253
6741
  ERROR_BANNER = /(client-side exception|application error|something (?:went wrong|broke)|unhandled (?:exception|error|rejection)|runtime error|error boundary|an unexpected error|internal server error|\b5\d\d\s+(?:internal\s+)?server error|stack trace|chunkloaderror|hydration failed|maximum update depth exceeded)/i;
6254
6742
  OUTCOME_ALWAYS = [
6255
6743
  /\.toHaveCount\s*\(/,
@@ -6266,7 +6754,7 @@ var init_assertion_strength = __esm({
6266
6754
  // form field value
6267
6755
  /\.toMatch\s*\(/,
6268
6756
  // string/regex content match (e.g. textContent() vs /[2-9] selected/)
6269
- /(?<!\.not\s*)\.\s*toBeDisabled\s*\(/,
6757
+ /\.(?<!\.not\s*\.)\s*toBeDisabled\s*\(/,
6270
6758
  // a control became DISABLED — a gating/validation outcome (not.toBeDisabled is the toBeEnabled twin)
6271
6759
  /\.not\.toBeVisible\s*\(/,
6272
6760
  /\.toBeHidden\s*\(/
@@ -6275,12 +6763,16 @@ var init_assertion_strength = __esm({
6275
6763
  FORM_INPUT = /\.(fill|selectOption|setInputFiles|check|uncheck|setChecked|pressSequentially)\s*\(|keyboard\.type\s*\(/;
6276
6764
  ANY_CLICK = /\.click\s*\(/;
6277
6765
  NAV_LOCATOR = /getByRole\s*\(\s*['"`](?:link|menuitem)['"`]/i;
6278
- URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(|\bexpect\s*\(\s*(?:await\s+)?page\s*\.\s*url\s*\(\s*\)\s*\)|\bexpect\s*\.\s*poll\s*\(\s*(?:async\s*)?\(\s*\)\s*=>\s*(?:\{\s*return\s+)?page\s*\.\s*url\s*\(\s*\)/;
6766
+ URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(|\bexpect\s*\(\s*(?:await\s+)?page\s*\.\s*url\s*\(\s*\)\s*\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
6767
+ POLL_OPEN = /\bexpect\s*\.\s*poll\s*\(/g;
6768
+ POLL_PAGE_URL = /^page\s*\.\s*url\s*\(\s*\)/;
6769
+ POLL_PAGE_URL_ONLY = /^page\s*\.\s*url\s*\(\s*\)$/;
6770
+ POLL_CALLS_MAX = 8;
6279
6771
  RELOAD = /\.reload\s*\(/;
6280
6772
  EXPECT_STMT = /\bexpect\s*[.(]/;
6281
6773
  BLOCK_SCAFFOLD2 = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
6282
6774
  TEST_BLOCK_OPEN = /^(?:test|it)(?:\.(?:only|skip|fixme|slow|serial|describe\.serial))?\s*\(|^(?:test\.)?(?:beforeEach|beforeAll)\s*\(/;
6283
- LOCATOR_BINDING = /^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(.+?(?:getByRole|getByText|getByLabel|getByPlaceholder|getByTestId|getByAltText|getByTitle|locator)\s*\(.+)$/;
6775
+ LOCATOR_BINDING = /^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=(?:\s*(?!\s)|\s*(?=\s(?:getByRole|getByText|getByLabel|getByPlaceholder|getByTestId|getByAltText|getByTitle|locator)\s*\())(.+?(?:getByRole|getByText|getByLabel|getByPlaceholder|getByTestId|getByAltText|getByTitle|locator)\s*\(.+)$/;
6284
6776
  EXPECT_OPEN = /\bexpect(?:\.soft)?\s*\(/;
6285
6777
  STATE_MATCHER = /\.(toBeChecked|toBeVisible|toHaveValue|toBeDisabled|toBeEnabled)\s*\(/;
6286
6778
  CHROME_LOCATOR = /getByRole\s*\(\s*['"`](?:heading|navigation|banner|main|contentinfo|complementary|region|search|tablist|tab|button|link|menuitem|textbox|searchbox|combobox|spinbutton)['"`]/i;
@@ -6300,7 +6792,7 @@ var init_assertion_strength = __esm({
6300
6792
  EMPTY_STATE_TEXT = /\bno\b[\s\S]{0,24}?\b(yet|found|results?|items?|favou?rites?|entries|records?|matches|posts?|comments?)\b|nothing (?:here|yet|to show|found)|\bempty\b|\(\s*0\s*\)|\b0\s+(?:favou?rites?|results?|items?|entries|records?|matches|posts?|comments?)\b/i;
6301
6793
  AUTH_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_AUTH/;
6302
6794
  AUTH_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_AUTH/;
6303
- LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*;?\s*$/;
6795
+ LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*(?:;\s*)?$/;
6304
6796
  INBOX_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_INBOX/;
6305
6797
  INBOX_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_INBOX/;
6306
6798
  NEGATED_ASSERT = /\.not\s*\.\s*to[A-Z]\w*|\.toBeHidden\s*\(/;
@@ -6310,7 +6802,7 @@ var init_assertion_strength = __esm({
6310
6802
  TITLE_LINE = /^\s*(?:test|describe)(?:\.\w+)?\s*\(/;
6311
6803
  FAILURE_STATUS_ASSERT = [
6312
6804
  /\.status\s*\(\s*\)[^;]{0,200}\.(?:toBeGreaterThanOrEqual|toBeGreaterThan)\s*\(\s*(?:399|[45]\d\d)\b/,
6313
- /\bexpect\s*\(\s*[^;]{0,200}\bstatus\s*(?:\(\s*\))?\s*>=?\s*(?:399|[45]\d\d)\b[^;]{0,200}\)\s*\.\s*(?:toBe\s*\(\s*true|toBeTruthy)/,
6805
+ /\bexpect\s*\(\s*[^;]{0,200}\bstatus\s*(?:\(\s*\)\s*)?>=?\s*(?:399|[45]\d\d)\b[^;]{0,200}\)\s*\.\s*(?:toBe\s*\(\s*true|toBeTruthy)/,
6314
6806
  /\bexpect\s*\([^;]{0,200}\.ok\s*\(\s*\)\s*\)\s*\.\s*(?:toBe\s*\(\s*false\s*\)|toBeFalsy\s*\()/
6315
6807
  ];
6316
6808
  FAILED_LIST_ASSERT = /\bexpect\s*\(\s*[^;]{0,200}(?:fail|abort|error)\w{0,40}(?:\.length)?\s*\)\s*\.\s*(?:toBeGreaterThan\s*\(\s*0|toHaveLength\s*\(\s*[1-9]|not\s*\.\s*toHaveLength\s*\(\s*0|not\s*\.\s*toEqual\s*\(\s*\[\s*\])/i;
@@ -6325,6 +6817,7 @@ var init_assertion_strength = __esm({
6325
6817
  NOT_BEFORE_MATCHER = /\.not\s*\.\s*(?=to[A-Z])/g;
6326
6818
  ERROR_TEXT_WORDS = /error|fail|wrong|try again|unable|could ?n.?t|can.?t|problem|invalid|unavailable/i;
6327
6819
  TEXT_REGEX_ARG = /(?:getByText|hasText|getByRole\([^)]{0,200}name)\s*[:(]\s*(\/(?:\\.|[^/\\\n])+\/[a-z]*)/;
6820
+ SCOPING_STEP = /(?:\s*\.\s*(?:first|last|nth)\s*\([^()]*\))*\s*\.\s*(?:getBy[A-Z]\w*|locator|filter)\s*\(/y;
6328
6821
  NEGATED_TEXT = /\.not\s*\.\s*(?:toHaveText|toContainText)\s*\(\s*(?:['"`]([^'"`]*)['"`]|\/((?:\\.|[^/\\\n])+)\/)?/;
6329
6822
  ASYNC_DESTRUCTURE_OPEN = /async\s*\(\s*\{/g;
6330
6823
  REQUEST_WORD = /\brequest\b/;
@@ -6351,6 +6844,9 @@ var init_assertion_strength = __esm({
6351
6844
  ENV_READ = String.raw`process\.env(?:(?:\?\.|\.)([A-Za-z_]\w*)|(?:\?\.)?\[\s*['"]([A-Za-z_]\w*)['"]\s*\])`;
6352
6845
  ENTERED_ENV = new RegExp(String.raw`\.(?:fill|pressSequentially)\s*\(\s*${ENV_READ}|keyboard\.type\s*\(\s*${ENV_READ}`, "g");
6353
6846
  HAVE_VALUE_ENV = new RegExp(String.raw`\.toHaveValue\s*\(\s*${ENV_READ}`);
6847
+ ACCOUNT_KEYS = TEST_USER_ENV_KEYS.join("|");
6848
+ ACCOUNT_ENV = `process\\.env(?:\\??\\.(?:${ACCOUNT_KEYS})\\b|(?:\\?\\.)?\\[\\s*['"\`](?:${ACCOUNT_KEYS})['"\`]\\s*\\])`;
6849
+ ACCOUNT_CONST = new RegExp(`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*(?::\\s*string\\s*)?=\\s*${ACCOUNT_ENV}`, "g");
6354
6850
  IN_FLIGHT_TEXT = /^(?:(?:sending|saving|loading|searching|checking|processing|submitting|preparing|updating|please wait|working|one moment)\b[^.]{0,40}\.{2,3}|\.{3}|…)$/i;
6355
6851
  LOCATOR_VALUE_READ = /\.(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts|evaluate|evaluateAll|boundingBox|all)\s*\(/;
6356
6852
  LOCATOR_EXPR = /^(?:this\.)?page\s*\.\s*(?:getBy\w+|locator)\s*\(/;
@@ -6362,20 +6858,47 @@ var init_assertion_strength = __esm({
6362
6858
  IS_VISIBLE_TRUE = /^\)*\s*\.\s*(?:toBe\s*\(\s*true\s*\)|toBeTruthy\s*\(\s*\))/;
6363
6859
  PAGE_VALUE_MATCHER = /^\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
6364
6860
  COUNT_ONE = /\.\s*toHaveCount\s*\(\s*1\s*\)/;
6861
+ URL_CHECK_ARG = /\bpage\s*\.\s*waitForURL\s*\(\s*([^,)]+)|\bexpect(?:\s*\.\s*soft)?\s*\(\s*page\s*\)\s*\.\s*toHaveURL\s*\(\s*([^,)]+)/;
6862
+ PLAIN_PAGE_PATH = /^\/[\w\-./~%]*(?:[?#][^\s]*)?$/;
6365
6863
  FORM_CONTROL_LOCATOR = /getByRole\s*\(\s*['"`](?:combobox|switch|checkbox|radio|textbox|searchbox|spinbutton|slider)['"`]|getByLabel\s*\(|getByPlaceholder\s*\(/;
6366
6864
  CONTROL_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(|\.toHaveAttribute\s*\(\s*['"`]aria-checked['"`]/;
6865
+ ACCOUNT_EMAIL_ENV = /^process\.env(?:(?:\?\.|\.)TEST_USER_EMAIL\b|(?:\?\.)?\[\s*['"]TEST_USER_EMAIL['"]\s*\])/;
6866
+ ENV_PASSWORD_ENTRY = /\.(?:fill|pressSequentially)\s*\(\s*process\.env(?:(?:\?\.|\.)TEST_USER_PASSWORD\b|(?:\?\.)?\[\s*['"]TEST_USER_PASSWORD['"]\s*\])/;
6867
+ ACCOUNT_CONTROL_ROLE = /getByRole\s*\(\s*['"`](?:button|link|menuitem)['"`]\s*,\s*\{/;
6868
+ ASSERT_OR_WAIT = /^(?:await\s+)?(?:expect(?:\.soft|\.poll)?\s*\(|[\w$]+(?:\.[\w$]+)*\.waitFor[A-Za-z]*\s*\()/;
6869
+ ACTS = /\.(?:click|dblclick|tap|press|pressSequentially|fill|clear|type|check|uncheck|setChecked|selectOption|setInputFiles|dispatchEvent|hover|focus|dragTo|goto|reload|goBack|goForward|evaluate|evaluateHandle)\s*\(|\b(?:keyboard|mouse|touchscreen)\s*\.|\.toPass\s*\(/;
6870
+ actLinesOf = /* @__PURE__ */ new WeakMap();
6871
+ CLOSERS_ONLY = /^[})\];,\s]+$/;
6872
+ TEST_BODY_OPEN = /^(?:test|it)(?:\.(?:only|skip|fixme|slow))?\s*\(/;
6367
6873
  COPY_CONTROL = /\bcopy\b/i;
6368
6874
  COPIED_NOTICE = /\bcopied\b/i;
6369
6875
  SEND_CONTROL = /\b(send|submit|subscribe|sign up for|contact|feedback|join (the )?waitlist)\b/i;
6370
6876
  SEND_NOTICE = /\b(thank|thanks|sent|subscribed|received|in touch)\b/i;
6371
6877
  MARK_READ_CONTROL = /\bmark(?:ed)? as read\b|\bmark all(?: as)? read\b/i;
6372
6878
  APP_DATA_FIELD = /\b(title|name of|amount|price|quantity|sku|status|due|assignee|project|ticket|invoice|order)\b/i;
6879
+ COMMIT_CONTROL = /\b(?:save|submit|update|apply|add|create|post|send|publish)\b|\bconfirm\s+changes\b/i;
6880
+ NOT_COMMIT_CONTROL = /\b(?:delete|remove|cancel|close|dismiss|discard|back|sign\s*out|log\s*out|logout|done|ok|got\s+it|export|download)\b/i;
6881
+ DESTRUCTIVE_CONTROL = /\b(?:delete|remove|discard)\b/i;
6882
+ ENTER_PRESS = /\.press\s*\(\s*['"`]Enter['"`]/;
6883
+ CONTROL_LABEL = /(?:name\s*:\s*|getBy(?:Text|Label|Title|TestId)\s*\(\s*|has-text\(\s*)(?:(['"`])((?:(?!\1).)*)\1|\/((?:\\.|[^/\\])+)\/)/g;
6884
+ isCommitClick = (stmt) => {
6885
+ const label = clickLabel(stmt);
6886
+ return label !== null && COMMIT_CONTROL.test(label) && !NOT_COMMIT_CONTROL.test(label) && !EXCLUDE_AFFORDANCE.test(label);
6887
+ };
6888
+ isDestructiveClick = (stmt) => {
6889
+ const label = clickLabel(stmt);
6890
+ return label !== null && DESTRUCTIVE_CONTROL.test(label);
6891
+ };
6892
+ TEARDOWN_OPEN = /\bfinally\s*\{|^(?:test\.)?(?:after|before)(?:Each|All)\s*\(/;
6373
6893
  }
6374
6894
  });
6375
6895
 
6376
6896
  // ../shared/dist/run-classification.js
6377
- function isUserCancelledError(error2) {
6378
- return typeof error2 === "string" && error2.trim().startsWith(USER_CANCELLED_RUN_ERROR);
6897
+ function isCancelledReason(error2) {
6898
+ if (typeof error2 !== "string")
6899
+ return false;
6900
+ const text = error2.trim();
6901
+ return CANCELLED_REASON_PREFIXES.some((prefix) => text.startsWith(prefix));
6379
6902
  }
6380
6903
  function isHeldHealRun(run) {
6381
6904
  return run?.status === "PASSED" && typeof run.error === "string" && run.error.startsWith(HEAL_PATCH_HELD_RUN_ERROR_PREFIX);
@@ -6408,7 +6931,7 @@ function pickLatestHealthRun(runs) {
6408
6931
  for (const run of runs) {
6409
6932
  if (run.mode === "auth-setup")
6410
6933
  continue;
6411
- if (run.status === "ERROR" && isUserCancelledError(run.error))
6934
+ if (run.status === "ERROR" && isCancelledReason(run.error))
6412
6935
  continue;
6413
6936
  if (isHeldHealRun(run))
6414
6937
  continue;
@@ -6426,6 +6949,15 @@ function pickDisplayRun(runs) {
6426
6949
  return latest;
6427
6950
  return pickLatestHealthRun(runs) ?? latest;
6428
6951
  }
6952
+ function featureRowRunStatus(runs) {
6953
+ const latest = runs.find((run) => run.mode !== "auth-setup");
6954
+ if (!latest)
6955
+ return null;
6956
+ const decisive = pickLatestHealthRun(runs.filter((run) => !isEnvironmentalError(run)));
6957
+ if (decisive)
6958
+ return decisive.status;
6959
+ return latest.status === "PENDING" || latest.status === "RUNNING" ? latest.status : null;
6960
+ }
6429
6961
  function isEnvironmentalError(run) {
6430
6962
  if (!run)
6431
6963
  return false;
@@ -6457,7 +6989,7 @@ function classifyErrorMessage(error2) {
6457
6989
  return "config_issue";
6458
6990
  if (EMAIL_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
6459
6991
  return "email_not_received";
6460
- if (isUserCancelledError(error2))
6992
+ if (isCancelledReason(error2))
6461
6993
  return "cancelled";
6462
6994
  if (isRetriedRunInterruption(error2))
6463
6995
  return "interrupted";
@@ -6492,7 +7024,7 @@ function heldHealRunWhere() {
6492
7024
  function excludeCancelledRunsWhere() {
6493
7025
  return {
6494
7026
  NOT: [
6495
- { status: "ERROR", error: { not: null, startsWith: USER_CANCELLED_RUN_ERROR } },
7027
+ { status: "ERROR", OR: CANCELLED_REASON_PREFIXES.map((prefix) => ({ error: { not: null, startsWith: prefix } })) },
6496
7028
  heldHealRunWhere()
6497
7029
  ]
6498
7030
  };
@@ -6570,11 +7102,25 @@ function reportedBugWhere() {
6570
7102
  function bugReviewStatus(status) {
6571
7103
  return status === "QUARANTINED" ? "confirmed" : "needs-review";
6572
7104
  }
6573
- var USER_CANCELLED_RUN_ERROR, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, TEST_DID_NOT_RUN_ERROR, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
7105
+ var USER_CANCELLED_RUN_ERROR, PLATFORM_SUPPORT_CANCELLED_RUN_ERROR, PLATFORM_ADMIN_CANCELLED_RUN_ERROR, ADMIN_FORCE_CANCELLED_RUN_ERROR, CHAIN_CANCELLED_RUN_ERROR, RETRY_HELD_RUN_ERROR, CANCELLED_REASON_PREFIXES, isUserCancelledError, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, TEST_DID_NOT_RUN_ERROR, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, FEATURE_ROW_RUN_WINDOW, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
6574
7106
  var init_run_classification = __esm({
6575
7107
  "../shared/dist/run-classification.js"() {
6576
7108
  "use strict";
6577
7109
  USER_CANCELLED_RUN_ERROR = "Cancelled by user";
7110
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR = "Cancelled by platform support";
7111
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR = "Cancelled by platform admin";
7112
+ ADMIN_FORCE_CANCELLED_RUN_ERROR = "Admin force-cancelled all active runs";
7113
+ CHAIN_CANCELLED_RUN_ERROR = "Cancelled: parent run cancelled";
7114
+ RETRY_HELD_RUN_ERROR = "Cancelled: auto generation paused";
7115
+ CANCELLED_REASON_PREFIXES = [
7116
+ USER_CANCELLED_RUN_ERROR,
7117
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR,
7118
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR,
7119
+ ADMIN_FORCE_CANCELLED_RUN_ERROR,
7120
+ CHAIN_CANCELLED_RUN_ERROR,
7121
+ RETRY_HELD_RUN_ERROR
7122
+ ];
7123
+ isUserCancelledError = isCancelledReason;
6578
7124
  HEAL_PATCH_HELD_RUN_ERROR_PREFIX = "Heal patch held";
6579
7125
  RUNNER_INTERRUPT_ERROR_PREFIX = "Runner shutting down";
6580
7126
  SERVER_INTERRUPT_ERROR_PREFIX = "Run interrupted by server restart";
@@ -6603,9 +7149,10 @@ var init_run_classification = __esm({
6603
7149
  ];
6604
7150
  ENVIRONMENTAL_ERROR_STORED_PREFIXES = [
6605
7151
  ...ENVIRONMENTAL_ERROR_PREFIXES.flatMap((prefix) => [prefix, `Error: ${prefix}`]),
6606
- // Written by the cancel routes as is, with no prefix wrapper.
6607
- USER_CANCELLED_RUN_ERROR
7152
+ // Written by the cancels as is, with no prefix wrapper.
7153
+ ...CANCELLED_REASON_PREFIXES
6608
7154
  ];
7155
+ FEATURE_ROW_RUN_WINDOW = 10;
6609
7156
  BUG_SURFACE_SUPPRESSING_TAGS = ["@rate-limit-blocked", "@quota-exceeded", "@auth-lost"];
6610
7157
  QUARANTINE_CLOSE_TAGS = [
6611
7158
  "@bug-found",
@@ -9014,201 +9561,27 @@ function sanitizeAdminViewReturnTo(value) {
9014
9561
  return ADMIN_VIEW_DEFAULT_RETURN_TO;
9015
9562
  let url;
9016
9563
  try {
9017
- url = new URL(trimmed, PLACEHOLDER_ORIGIN);
9018
- } catch {
9019
- return ADMIN_VIEW_DEFAULT_RETURN_TO;
9020
- }
9021
- if (url.origin !== PLACEHOLDER_ORIGIN)
9022
- return ADMIN_VIEW_DEFAULT_RETURN_TO;
9023
- if (BLOCKED_PATH_RE.test(url.pathname))
9024
- return ADMIN_VIEW_DEFAULT_RETURN_TO;
9025
- const out = `${url.pathname}${url.search}${url.hash}`;
9026
- if (out.startsWith("//") || out.includes("\\"))
9027
- return ADMIN_VIEW_DEFAULT_RETURN_TO;
9028
- return out;
9029
- }
9030
- var ADMIN_VIEW_DEFAULT_RETURN_TO, MAX_RETURN_TO_LENGTH, PLACEHOLDER_ORIGIN, BLOCKED_PATH_RE;
9031
- var init_admin_view_return_to = __esm({
9032
- "../shared/dist/admin-view-return-to.js"() {
9033
- "use strict";
9034
- ADMIN_VIEW_DEFAULT_RETURN_TO = "/sessions";
9035
- MAX_RETURN_TO_LENGTH = 2048;
9036
- PLACEHOLDER_ORIGIN = "https://admin-view.invalid";
9037
- BLOCKED_PATH_RE = /^\/(?:login|admin-view)/i;
9038
- }
9039
- });
9040
-
9041
- // ../shared/dist/auth-plan.js
9042
- function hasInlineLoginSteps(playwrightCode) {
9043
- if (!playwrightCode)
9044
- return false;
9045
- return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
9046
- }
9047
- function hasGeneratedAuthGuard(playwrightCode) {
9048
- if (!playwrightCode)
9049
- return false;
9050
- return /\/\/ >>> VALIDATE_QA_AUTH v(\d+)\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\1\b/.test(playwrightCode);
9051
- }
9052
- function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
9053
- if (!sharedAuthInjectEnabled)
9054
- return false;
9055
- if (tags.includes("@auth"))
9056
- return false;
9057
- if (tags.includes("@api"))
9058
- return true;
9059
- if (!tags.includes("@needs-auth"))
9060
- return false;
9061
- if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
9062
- return true;
9063
- const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
9064
- const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
9065
- return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
9066
- }
9067
- function codeReadsStoredCredentials(code) {
9068
- if (!code)
9069
- return false;
9070
- if (/__vqaLogin\b/.test(code))
9071
- return true;
9072
- for (const m of code.matchAll(/TEST_[A-Z0-9_]+/g)) {
9073
- if (/_(?:EMAIL|PASSWORD)/.test(m[0]))
9074
- return true;
9075
- }
9076
- for (const m of code.matchAll(/process\.env\s*\[([^\]]*)\]?/g)) {
9077
- if (!/^\s*(['"])[^'"`]*\1\s*$/.test(m[1]))
9078
- return true;
9079
- }
9080
- return false;
9081
- }
9082
- function explicitAuthTypeTags(t) {
9083
- const found = /* @__PURE__ */ new Set();
9084
- for (const tag of t) {
9085
- if (AUTH_TYPE_TAGS.includes(tag))
9086
- found.add(tag);
9087
- else if (Object.hasOwn(AUTH_TAG_ALIASES, tag))
9088
- found.add(AUTH_TAG_ALIASES[tag]);
9089
- }
9090
- return [...found];
9091
- }
9092
- function invalidPlan(configurationError) {
9093
- return {
9094
- version: 1,
9095
- source: "invalid",
9096
- acquisition: "unsupported",
9097
- presentation: "none",
9098
- needsCookieState: false,
9099
- needsStandingHeaders: false,
9100
- standingHeadersMode: "none",
9101
- needsLoginCreds: false,
9102
- requiredEnvKeys: [],
9103
- configurationError
9104
- };
9105
- }
9106
- function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
9107
- return {
9108
- version: 1,
9109
- source: "explicit",
9110
- authTypeTag: authTypeTag2,
9111
- acquisition,
9112
- presentation,
9113
- needsCookieState: opts.cookie,
9114
- needsStandingHeaders: opts.standingMode === "required",
9115
- standingHeadersMode: opts.standingMode,
9116
- needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
9117
- requiredEnvKeys: opts.requiredEnvKeys
9118
- };
9119
- }
9120
- function deriveAuthPlan(tags, code, opts) {
9121
- const t = Array.isArray(tags) ? tags : [];
9122
- const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
9123
- const isApi = t.includes("@api");
9124
- const explicit = isApi ? explicitAuthTypeTags(t) : [];
9125
- if (explicit.length > 0) {
9126
- if (t.includes("@auth")) {
9127
- return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
9128
- }
9129
- const loginCompose = explicit.filter((e) => e === AUTH_TAG_COOKIE_STATE || e === AUTH_TAG_LOGIN_COOKIE || e === AUTH_TAG_LOGIN_BEARER);
9130
- if (explicit.length === 2 && explicit.includes(AUTH_TAG_STANDING) && loginCompose.length === 1) {
9131
- const composedKeys = [...TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY];
9132
- switch (loginCompose[0]) {
9133
- case AUTH_TAG_COOKIE_STATE:
9134
- return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "required", requiredEnvKeys: composedKeys });
9135
- case AUTH_TAG_LOGIN_COOKIE:
9136
- return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
9137
- case AUTH_TAG_LOGIN_BEARER:
9138
- return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
9139
- }
9140
- }
9141
- if (explicit.length > 1) {
9142
- return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
9143
- }
9144
- switch (explicit[0]) {
9145
- case AUTH_TAG_UNSUPPORTED:
9146
- return {
9147
- ...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
9148
- };
9149
- case AUTH_TAG_NONE:
9150
- return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
9151
- case AUTH_TAG_STANDING:
9152
- return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
9153
- case AUTH_TAG_LOGIN_BEARER:
9154
- return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
9155
- case AUTH_TAG_LOGIN_COOKIE:
9156
- return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
9157
- case AUTH_TAG_COOKIE_STATE:
9158
- return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
9159
- }
9160
- }
9161
- const cookie = legacyNeedsCookieState(t, code, sharedFlag);
9162
- const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
9163
- const selfLoginWithoutStoredCreds = t.includes("@auth") && !t.includes("@needs-auth") && !isApi && !!code?.trim() && !/TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin\b/.test(code);
9164
- const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
9165
- const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
9166
- return {
9167
- version: 1,
9168
- source: "legacy",
9169
- acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
9170
- presentation: authedLegacy ? "cookie" : "none",
9171
- needsCookieState: cookie,
9172
- needsStandingHeaders: false,
9173
- // A legacy bare-@api test keeps injecting standing headers IF the project configured them
9174
- // (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
9175
- standingHeadersMode: isApi ? "legacy-if-configured" : "none",
9176
- needsLoginCreds,
9177
- requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
9178
- };
9179
- }
9180
- function authTypeTag(tags) {
9181
- const t = Array.isArray(tags) ? tags : [];
9182
- const explicit = explicitAuthTypeTags(t);
9183
- return explicit.length === 1 ? explicit[0] : void 0;
9564
+ url = new URL(trimmed, PLACEHOLDER_ORIGIN);
9565
+ } catch {
9566
+ return ADMIN_VIEW_DEFAULT_RETURN_TO;
9567
+ }
9568
+ if (url.origin !== PLACEHOLDER_ORIGIN)
9569
+ return ADMIN_VIEW_DEFAULT_RETURN_TO;
9570
+ if (BLOCKED_PATH_RE.test(url.pathname))
9571
+ return ADMIN_VIEW_DEFAULT_RETURN_TO;
9572
+ const out = `${url.pathname}${url.search}${url.hash}`;
9573
+ if (out.startsWith("//") || out.includes("\\"))
9574
+ return ADMIN_VIEW_DEFAULT_RETURN_TO;
9575
+ return out;
9184
9576
  }
9185
- var AUTH_TAG_COOKIE_STATE, AUTH_TAG_LOGIN_COOKIE, AUTH_TAG_LOGIN_BEARER, AUTH_TAG_STANDING, AUTH_TAG_NONE, AUTH_TAG_UNSUPPORTED, AUTH_TYPE_TAGS, AUTH_TAG_ALIASES, TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY;
9186
- var init_auth_plan = __esm({
9187
- "../shared/dist/auth-plan.js"() {
9577
+ var ADMIN_VIEW_DEFAULT_RETURN_TO, MAX_RETURN_TO_LENGTH, PLACEHOLDER_ORIGIN, BLOCKED_PATH_RE;
9578
+ var init_admin_view_return_to = __esm({
9579
+ "../shared/dist/admin-view-return-to.js"() {
9188
9580
  "use strict";
9189
- AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
9190
- AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
9191
- AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
9192
- AUTH_TAG_STANDING = "@auth-standing";
9193
- AUTH_TAG_NONE = "@auth-none";
9194
- AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
9195
- AUTH_TYPE_TAGS = [
9196
- AUTH_TAG_COOKIE_STATE,
9197
- AUTH_TAG_LOGIN_COOKIE,
9198
- AUTH_TAG_LOGIN_BEARER,
9199
- AUTH_TAG_STANDING,
9200
- AUTH_TAG_NONE,
9201
- AUTH_TAG_UNSUPPORTED
9202
- ];
9203
- AUTH_TAG_ALIASES = {
9204
- "@auth-cookie": AUTH_TAG_COOKIE_STATE,
9205
- "@auth-bearer": AUTH_TAG_LOGIN_BEARER,
9206
- "@auth-token": AUTH_TAG_LOGIN_BEARER,
9207
- "@auth-api-key": AUTH_TAG_STANDING,
9208
- "@auth-standing-header": AUTH_TAG_STANDING
9209
- };
9210
- TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
9211
- API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
9581
+ ADMIN_VIEW_DEFAULT_RETURN_TO = "/sessions";
9582
+ MAX_RETURN_TO_LENGTH = 2048;
9583
+ PLACEHOLDER_ORIGIN = "https://admin-view.invalid";
9584
+ BLOCKED_PATH_RE = /^\/(?:login|admin-view)/i;
9212
9585
  }
9213
9586
  });
9214
9587
 
@@ -9219,13 +9592,16 @@ function stripEchoLines(text) {
9219
9592
  function matchesAny(text, patterns) {
9220
9593
  return patterns.some((p) => p.test(text));
9221
9594
  }
9595
+ function isIntentionalNegativeAuthTest(input) {
9596
+ const meta = [input.testName, input.description].filter(Boolean).join(" ");
9597
+ return matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH);
9598
+ }
9222
9599
  function detectAuthBlock(input) {
9223
9600
  const tags = input.tags ?? [];
9224
9601
  const isAuthed = tags.some((t) => AUTHED_TAGS.includes(t));
9225
9602
  if (!isAuthed)
9226
9603
  return NO_DETECTION;
9227
- const meta = [input.testName, input.description].filter(Boolean).join(" ");
9228
- if (matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH))
9604
+ if (isIntentionalNegativeAuthTest(input))
9229
9605
  return NO_DETECTION;
9230
9606
  const sources = [];
9231
9607
  if (input.error)
@@ -9328,7 +9704,7 @@ var init_auth_block_detector = __esm({
9328
9704
  /\b(reject|deny|denied|block(ed)?)\b[^\n]{0,20}\b(login|sign[\s-]?in|auth|credential|password)\b/i
9329
9705
  ];
9330
9706
  ECHO_LINE_RE = /^\s*(?:(?:📋|🎭|🌐|⚙️?)|(?:stderr:\s*|stdout:\s*)?Locator:)/u;
9331
- UNAUTHORIZED_TEXT_RE = /(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|gives?|sends?|yield(?:s|ed|ing)?|results?\s+in)\s+(?:a\s+|an\s+|the\s+)?(?:401|403)\b|\bstatus(?:\s+code)?\s*:?\s*(?:401|403)\b|\bHTTP\/[\d.]+\s+(?:401|403)\b|(?:401|403)['’]d\b|(?:[→⇒]|->)\s*(?:401|403)\b|(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|threw|throws?)\s+(?:a\s+|an\s+)?(?:unauthorized|forbidden)\b|\bnot\s+authenticated\b/i;
9707
+ UNAUTHORIZED_TEXT_RE = /(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|gives?|sends?|yield(?:s|ed|ing)?|results?\s+in)\s+(?:a\s+|an\s+|the\s+)?(?:401|403)\b|\bstatus(?:\s+code)?\s*(?::\s*)?(?:401|403)\b|\bHTTP\/[\d.]+\s+(?:401|403)\b|(?:401|403)['’]d\b|(?:[→⇒]|->)\s*(?:401|403)\b|(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|threw|throws?)\s+(?:a\s+|an\s+)?(?:unauthorized|forbidden)\b|\bnot\s+authenticated\b/i;
9332
9708
  }
9333
9709
  });
9334
9710
 
@@ -10269,12 +10645,70 @@ function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
10269
10645
  function isAuthReadinessGuardEnabled(value = process.env.AUTH_READINESS_GUARD) {
10270
10646
  return value?.trim().toLowerCase() !== "false";
10271
10647
  }
10648
+ function visualBaselinesEnabled(projectId, raw = process.env.VISUAL_BASELINES_PROJECTS) {
10649
+ if (typeof projectId !== "string" || projectId.trim() === "")
10650
+ return false;
10651
+ const listed = (raw ?? "").split(",").map((entry) => entry.trim()).filter(Boolean);
10652
+ return listed.includes("*") || listed.includes(projectId);
10653
+ }
10272
10654
  var init_feature_flags = __esm({
10273
10655
  "../shared/dist/feature-flags.js"() {
10274
10656
  "use strict";
10275
10657
  }
10276
10658
  });
10277
10659
 
10660
+ // ../shared/dist/visual-compare-limits.js
10661
+ function isSafeMaskSelector(selector) {
10662
+ if (typeof selector !== "string")
10663
+ return false;
10664
+ if (selector.trim().length === 0)
10665
+ return false;
10666
+ if (selector.length > VISUAL_MASK_SELECTOR_MAX_LENGTH)
10667
+ return false;
10668
+ return !VISUAL_MASK_SELECTOR_FORBIDDEN.test(selector);
10669
+ }
10670
+ var VISUAL_MASK_SELECTORS_MAX, VISUAL_MASK_SELECTOR_MAX_LENGTH, VISUAL_MASK_SELECTOR_FORBIDDEN;
10671
+ var init_visual_compare_limits = __esm({
10672
+ "../shared/dist/visual-compare-limits.js"() {
10673
+ "use strict";
10674
+ VISUAL_MASK_SELECTORS_MAX = 50;
10675
+ VISUAL_MASK_SELECTOR_MAX_LENGTH = 300;
10676
+ VISUAL_MASK_SELECTOR_FORBIDDEN = /[{};@<\r\n]/;
10677
+ }
10678
+ });
10679
+
10680
+ // ../shared/dist/visual-baseline-identity.js
10681
+ function isBrowserVersion(value) {
10682
+ return typeof value === "string" && value.length <= 32 && BROWSER_VERSION.test(value);
10683
+ }
10684
+ function renderIdentity(flavour, version, image) {
10685
+ if (!/^[a-z][a-z-]{0,31}$/.test(flavour) || !/^[a-z][a-z-]{0,31}$/.test(image) || !isBrowserVersion(version))
10686
+ return null;
10687
+ const identity = `${flavour}/${version}/${image}`;
10688
+ return identity.length <= RENDER_IDENTITY_MAX_LENGTH ? identity : null;
10689
+ }
10690
+ function v1RenderIdentity(version) {
10691
+ return renderIdentity(VISUAL_V1_IDENTITY.flavour, version, VISUAL_V1_IDENTITY.image);
10692
+ }
10693
+ function parseRenderIdentity(value) {
10694
+ if (typeof value !== "string" || value.length > RENDER_IDENTITY_MAX_LENGTH)
10695
+ return null;
10696
+ const parts = value.split("/");
10697
+ if (parts.length !== 3)
10698
+ return null;
10699
+ const [flavour, version, image] = parts;
10700
+ return renderIdentity(flavour, version, image) === value ? { flavour, version, image } : null;
10701
+ }
10702
+ var VISUAL_V1_IDENTITY, RENDER_IDENTITY_MAX_LENGTH, BROWSER_VERSION;
10703
+ var init_visual_baseline_identity = __esm({
10704
+ "../shared/dist/visual-baseline-identity.js"() {
10705
+ "use strict";
10706
+ VISUAL_V1_IDENTITY = { platform: "linux", image: "runner-fast", flavour: "headless-shell" };
10707
+ RENDER_IDENTITY_MAX_LENGTH = 64;
10708
+ BROWSER_VERSION = /^\d{1,4}(\.\d{1,6}){0,3}$/;
10709
+ }
10710
+ });
10711
+
10278
10712
  // ../shared/dist/analytics-url.js
10279
10713
  function isSensitiveQueryKey(key) {
10280
10714
  return SENSITIVE_QUERY_KEY_RE.test(key);
@@ -10628,14 +11062,9 @@ function isOpenCodeGoModelId(id) {
10628
11062
  function hasOpenCodeGoPrefix(id) {
10629
11063
  return (id?.trim() ?? "").startsWith(OPENCODE_GO_PREFIX);
10630
11064
  }
10631
- function hasOpenCodeGo2Prefix(id) {
10632
- return (id?.trim() ?? "").startsWith(OPENCODE_GO_2_PREFIX);
10633
- }
10634
11065
  function openCodeGoWireModel(id) {
10635
11066
  const trimmed = id?.trim() ?? "";
10636
- if (!isOpenCodeGoModelId(trimmed))
10637
- return null;
10638
- return trimmed.slice((hasOpenCodeGo2Prefix(trimmed) ? OPENCODE_GO_2_PREFIX : OPENCODE_GO_PREFIX).length);
11067
+ return isOpenCodeGoModelId(trimmed) ? trimmed.slice(OPENCODE_GO_PREFIX.length) : null;
10639
11068
  }
10640
11069
  function isZaiModelId(id) {
10641
11070
  const trimmed = id?.trim() ?? "";
@@ -10744,7 +11173,7 @@ function museCliContributorMode(env = process.env) {
10744
11173
  const raw = (env.MUSE_CLI_CONTRIBUTOR_MODE ?? "").trim().toLowerCase();
10745
11174
  return raw === "off" || raw === "false" || raw === "0" || raw === "no" ? "off" : "on";
10746
11175
  }
10747
- var CLI_ENGINE_MODEL_IDS, BROWSER_CLI_ENGINE_IDS, PIPELINE_STEPS, CLI_ENGINE_RULES, ENGINE_NAMES, OPENROUTER_PREFIX, OPENROUTER_MODEL_PATTERN, OPENCODE_GO_PREFIX, OPENCODE_GO_2_PREFIX, OPENCODE_GO_MODEL_PATTERN, ZAI_PREFIX, ZAI_MODEL_PATTERN;
11176
+ var CLI_ENGINE_MODEL_IDS, BROWSER_CLI_ENGINE_IDS, PIPELINE_STEPS, CLI_ENGINE_RULES, ENGINE_NAMES, OPENROUTER_PREFIX, OPENROUTER_MODEL_PATTERN, OPENCODE_GO_PREFIX, OPENCODE_GO_MODEL_PATTERN, ZAI_PREFIX, ZAI_MODEL_PATTERN;
10748
11177
  var init_engine_spec = __esm({
10749
11178
  "../shared/dist/engine-spec.js"() {
10750
11179
  "use strict";
@@ -10803,8 +11232,7 @@ var init_engine_spec = __esm({
10803
11232
  OPENROUTER_PREFIX = "openrouter:";
10804
11233
  OPENROUTER_MODEL_PATTERN = /^openrouter:[a-z0-9][a-z0-9._-]{0,40}\/[A-Za-z0-9._:-]{1,100}$/;
10805
11234
  OPENCODE_GO_PREFIX = "opencode-go:";
10806
- OPENCODE_GO_2_PREFIX = "opencode-go-2:";
10807
- OPENCODE_GO_MODEL_PATTERN = /^opencode-go(?:-2)?:[a-z0-9][a-z0-9._-]{0,100}$/;
11235
+ OPENCODE_GO_MODEL_PATTERN = /^opencode-go:[a-z0-9][a-z0-9._-]{0,100}$/;
10808
11236
  ZAI_PREFIX = "zai:";
10809
11237
  ZAI_MODEL_PATTERN = /^zai:glm-[a-z0-9][a-z0-9._-]{0,60}$/;
10810
11238
  }
@@ -11129,14 +11557,19 @@ function has(map2, key) {
11129
11557
  }
11130
11558
  function providerChainFor(modelId) {
11131
11559
  const key = chainKey(modelId);
11560
+ if (key && has(_overrideHops, key))
11561
+ return _overrideHops[key];
11132
11562
  return key && has(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
11133
11563
  }
11134
11564
  function providerChainLeadsFor(modelId) {
11135
11565
  const key = chainKey(modelId);
11566
+ if (key && has(_overrideLeads, key))
11567
+ return _overrideLeads[key];
11136
11568
  return key && has(PROVIDER_CHAIN_LEADS, key) ? PROVIDER_CHAIN_LEADS[key] : [];
11137
11569
  }
11138
11570
  function allChains() {
11139
- return [...Object.entries(PROVIDER_CHAINS), ...Object.entries(PROVIDER_CHAIN_LEADS)];
11571
+ const ordered = (map2, override) => [...Object.entries(map2).filter(([key]) => !has(override, key)), ...Object.entries(override)];
11572
+ return [...ordered(PROVIDER_CHAINS, _overrideHops), ...ordered(PROVIDER_CHAIN_LEADS, _overrideLeads)];
11140
11573
  }
11141
11574
  function isProviderChainHopId(modelId) {
11142
11575
  const id = modelId ?? "";
@@ -11153,14 +11586,24 @@ function providerChainHostOf(value, modelValue) {
11153
11586
  return "own";
11154
11587
  if (hasZaiPrefix(value))
11155
11588
  return "zai";
11156
- if (hasOpenCodeGo2Prefix(value))
11157
- return "opencode-go-2";
11158
11589
  if (hasOpenCodeGoPrefix(value))
11159
11590
  return "opencode-go";
11160
11591
  if (hasOpenRouterPrefix(value))
11161
11592
  return "openrouter";
11162
11593
  return "own";
11163
11594
  }
11595
+ function providerChainHostProvider(value) {
11596
+ if (typeof value !== "string" || !value)
11597
+ return null;
11598
+ if (hasOpenRouterPrefix(value))
11599
+ return "openrouter";
11600
+ if (hasOpenCodeGoPrefix(value))
11601
+ return "opencode-go";
11602
+ if (hasZaiPrefix(value))
11603
+ return "zai";
11604
+ const key = chainKey(value);
11605
+ return API_MODEL_CATALOG.find((m) => m.id === key)?.provider ?? GEMINI_ASSISTANT_MODELS.find((m) => m.id === key)?.provider ?? null;
11606
+ }
11164
11607
  function providerHostPlan(modelId) {
11165
11608
  const key = chainKey(modelId);
11166
11609
  if (!key)
@@ -11178,7 +11621,140 @@ function providerHostPlan(modelId) {
11178
11621
  }
11179
11622
  return steps;
11180
11623
  }
11181
- var PROVIDER_CHAINS, PROVIDER_CHAIN_LEADS, PROVIDER_HOST_NOT_OFFERED;
11624
+ function providerChainModelKey(modelId) {
11625
+ return chainKey(modelId);
11626
+ }
11627
+ function hasProviderChainLeadsByDefault(modelId) {
11628
+ const key = chainKey(modelId);
11629
+ return key !== null && has(PROVIDER_CHAIN_LEADS, key);
11630
+ }
11631
+ function isChainablePrimary(id) {
11632
+ return typeof id === "string" && id !== "" && (has(PROVIDER_CHAINS, id) || has(PROVIDER_CHAIN_LEADS, id));
11633
+ }
11634
+ function defaultProviderChainEntries(primary) {
11635
+ if (!isChainablePrimary(primary))
11636
+ return [];
11637
+ const leads = has(PROVIDER_CHAIN_LEADS, primary) ? PROVIDER_CHAIN_LEADS[primary] : [];
11638
+ const hops = has(PROVIDER_CHAINS, primary) ? PROVIDER_CHAINS[primary] : [];
11639
+ return [...leads, primary, ...hops].map((hop) => ({ hop, enabled: true }));
11640
+ }
11641
+ function allowedProviderChainHosts(primary) {
11642
+ return defaultProviderChainEntries(primary).map((entry) => entry.hop);
11643
+ }
11644
+ function isPlainObject2(value) {
11645
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
11646
+ }
11647
+ function validateProviderChainEntries(primary, entries) {
11648
+ if (!isChainablePrimary(primary))
11649
+ return `${String(primary).slice(0, 80)} has no provider chain to order`;
11650
+ if (!Array.isArray(entries))
11651
+ return `${primary}: the chain must be a list`;
11652
+ if (entries.length > PROVIDER_CHAIN_MAX_ENTRIES)
11653
+ return `${primary}: at most ${PROVIDER_CHAIN_MAX_ENTRIES} hosts`;
11654
+ const allowed = allowedProviderChainHosts(primary);
11655
+ const seen = /* @__PURE__ */ new Set();
11656
+ for (const entry of entries) {
11657
+ if (!isPlainObject2(entry))
11658
+ return `${primary}: each host must be { hop, enabled }`;
11659
+ const keys = Object.keys(entry);
11660
+ if (keys.length !== 2 || !keys.includes("hop") || !keys.includes("enabled"))
11661
+ return `${primary}: each host must be { hop, enabled } and nothing else`;
11662
+ const { hop, enabled } = entry;
11663
+ if (typeof enabled !== "boolean")
11664
+ return `${primary}: enabled must be true or false`;
11665
+ if (typeof hop !== "string" || !hop)
11666
+ return `${primary}: hop must be a model value`;
11667
+ if (!allowed.includes(hop))
11668
+ return `${primary}: ${hop.slice(0, 80)} is not a host of this model`;
11669
+ if (seen.has(hop))
11670
+ return `${primary}: ${hop} is listed twice`;
11671
+ if (hop === primary && !enabled)
11672
+ return `${primary}: the model's own value stays on (pick another model on AI Models instead)`;
11673
+ seen.add(hop);
11674
+ }
11675
+ if (!seen.has(primary))
11676
+ return `${primary}: list the model's own value once, where its own host sits`;
11677
+ return null;
11678
+ }
11679
+ function decodeProviderChains(raw) {
11680
+ let value = raw;
11681
+ if (typeof raw === "string") {
11682
+ if (raw.length > PROVIDER_CHAINS_SETTING_MAX_CHARS)
11683
+ return { ok: false, error: `AI_PROVIDER_CHAINS is too long (at most ${PROVIDER_CHAINS_SETTING_MAX_CHARS} characters)` };
11684
+ try {
11685
+ value = JSON.parse(raw);
11686
+ } catch {
11687
+ return { ok: false, error: "AI_PROVIDER_CHAINS must be valid JSON" };
11688
+ }
11689
+ } else if (JSON.stringify(raw ?? null).length > PROVIDER_CHAINS_SETTING_MAX_CHARS) {
11690
+ return { ok: false, error: `AI_PROVIDER_CHAINS is too long (at most ${PROVIDER_CHAINS_SETTING_MAX_CHARS} characters)` };
11691
+ }
11692
+ if (!isPlainObject2(value))
11693
+ return { ok: false, error: 'AI_PROVIDER_CHAINS must be an object: { "<model>": [ { "hop": "<model value>", "enabled": true } ] }' };
11694
+ if (Object.keys(value).length > PROVIDER_CHAIN_MAX_PRIMARIES)
11695
+ return { ok: false, error: `AI_PROVIDER_CHAINS names at most ${PROVIDER_CHAIN_MAX_PRIMARIES} models` };
11696
+ return { ok: true, value };
11697
+ }
11698
+ function checkedProviderChains(value) {
11699
+ const out = {};
11700
+ for (const [primary, entries] of Object.entries(value)) {
11701
+ const problem = validateProviderChainEntries(primary, entries);
11702
+ if (problem)
11703
+ return { ok: false, error: problem };
11704
+ out[primary] = entries.map(({ hop, enabled }) => ({ hop, enabled }));
11705
+ }
11706
+ return { ok: true, value: out };
11707
+ }
11708
+ function parseProviderChainOverrides(raw) {
11709
+ const decoded = decodeProviderChains(raw);
11710
+ return decoded.ok ? checkedProviderChains(decoded.value) : decoded;
11711
+ }
11712
+ function parseStoredProviderChainOverrides(raw) {
11713
+ const decoded = decodeProviderChains(raw);
11714
+ if (!decoded.ok)
11715
+ return decoded;
11716
+ const dropped = [];
11717
+ const kept = {};
11718
+ for (const [primary, entries] of Object.entries(decoded.value)) {
11719
+ if (!isChainablePrimary(primary) || !Array.isArray(entries)) {
11720
+ kept[primary] = entries;
11721
+ continue;
11722
+ }
11723
+ const allowed = allowedProviderChainHosts(primary);
11724
+ kept[primary] = entries.filter((entry) => {
11725
+ const hop = isPlainObject2(entry) ? entry.hop : void 0;
11726
+ if (typeof hop !== "string" || hop === "" || allowed.includes(hop))
11727
+ return true;
11728
+ dropped.push({ primary, hop: hop.slice(0, 80) });
11729
+ return false;
11730
+ });
11731
+ }
11732
+ const checked = checkedProviderChains(kept);
11733
+ return checked.ok ? { ...checked, dropped } : checked;
11734
+ }
11735
+ function validateProviderChainsSetting(raw) {
11736
+ const parsed = parseProviderChainOverrides(raw);
11737
+ return parsed.ok ? null : parsed.error;
11738
+ }
11739
+ function setProviderChainOverrides(value, onDropped) {
11740
+ const parsed = value === null || value === void 0 || value === "" ? null : parseStoredProviderChainOverrides(value);
11741
+ if (parsed?.ok && parsed.dropped.length > 0)
11742
+ onDropped?.(parsed.dropped);
11743
+ _overrides = parsed?.ok ? parsed.value : null;
11744
+ _overrideLeads = {};
11745
+ _overrideHops = {};
11746
+ for (const [primary, entries] of Object.entries(_overrides ?? {})) {
11747
+ const at = entries.findIndex((entry) => entry.hop === primary);
11748
+ const on = (list) => list.filter((entry) => entry.enabled).map((entry) => entry.hop);
11749
+ _overrideLeads[primary] = on(entries.slice(0, at));
11750
+ _overrideHops[primary] = on(entries.slice(at + 1));
11751
+ }
11752
+ return parsed === null || parsed.ok;
11753
+ }
11754
+ function getProviderChainOverrides() {
11755
+ return _overrides;
11756
+ }
11757
+ var PROVIDER_CHAINS, PROVIDER_CHAIN_LEADS, PROVIDER_HOST_NOT_OFFERED, PROVIDER_CHAIN_MAX_ENTRIES, PROVIDER_CHAINS_SETTING_MAX_CHARS, PROVIDER_CHAIN_MAX_PRIMARIES, ALLOWED_PROVIDER_CHAIN_HOPS, _overrides, _overrideLeads, _overrideHops;
11182
11758
  var init_provider_chains = __esm({
11183
11759
  "../shared/dist/provider-chains.js"() {
11184
11760
  "use strict";
@@ -11190,18 +11766,120 @@ var init_provider_chains = __esm({
11190
11766
  "muse-spark-1.3-contributor": ["openrouter:meta/muse-spark-1.3-contributor"],
11191
11767
  "claude-haiku-5-5": ["opencode-go:claude-haiku-5-5", "openrouter:anthropic/claude-haiku-5.5"],
11192
11768
  "gemini-3.1-pro-preview": ["openrouter:google/gemini-3.1-pro-preview"],
11193
- "deepseek-flash": ["opencode-go-2:deepseek-v4.1-flash", "opencode-go:deepseek-v4.1-flash", "openrouter:deepseek/deepseek-v4.1-flash"],
11194
- "deepseek-v4-pro": ["opencode-go-2:deepseek-v4-pro", "opencode-go:deepseek-v4-pro", "openrouter:deepseek/deepseek-v4-pro"]
11769
+ "deepseek-flash": ["opencode-go:deepseek-v4.1-flash", "openrouter:deepseek/deepseek-v4.1-flash"],
11770
+ "deepseek-v4-pro": ["opencode-go:deepseek-v4-pro", "openrouter:deepseek/deepseek-v4-pro"]
11195
11771
  };
11196
11772
  PROVIDER_CHAIN_LEADS = {
11197
- "openrouter:z-ai/glm-5.3-flash": ["zai:glm-5.3-flash", "opencode-go-2:glm-5.3-flash", "opencode-go:glm-5.3-flash"],
11198
- "openrouter:z-ai/glm-5.3": ["zai:glm-5.3", "opencode-go-2:glm-5.3", "opencode-go:glm-5.3"],
11199
- "openrouter:z-ai/glm-5.2": ["zai:glm-5.2", "opencode-go-2:glm-5.2", "opencode-go:glm-5.2"]
11773
+ "openrouter:z-ai/glm-5.3-flash": ["zai:glm-5.3-flash", "opencode-go:glm-5.3-flash"],
11774
+ "openrouter:z-ai/glm-5.3": ["zai:glm-5.3", "opencode-go:glm-5.3"],
11775
+ "openrouter:z-ai/glm-5.2": ["zai:glm-5.2", "opencode-go:glm-5.2"]
11200
11776
  };
11201
11777
  PROVIDER_HOST_NOT_OFFERED = {
11202
11778
  "muse-spark-1.3-contributor": { "opencode-go": "OpenCode Go serves Muse only on its Responses API" },
11203
11779
  "gemini-3.1-pro-preview": { "opencode-go": "OpenCode Go does not serve Gemini" }
11204
11780
  };
11781
+ PROVIDER_CHAIN_MAX_ENTRIES = 8;
11782
+ PROVIDER_CHAINS_SETTING_MAX_CHARS = 16e3;
11783
+ PROVIDER_CHAIN_MAX_PRIMARIES = 32;
11784
+ ALLOWED_PROVIDER_CHAIN_HOPS = /* @__PURE__ */ new Set([
11785
+ ...Object.values(PROVIDER_CHAINS).flat(),
11786
+ ...Object.values(PROVIDER_CHAIN_LEADS).flat()
11787
+ ]);
11788
+ _overrides = null;
11789
+ _overrideLeads = {};
11790
+ _overrideHops = {};
11791
+ }
11792
+ });
11793
+
11794
+ // ../shared/dist/provider-accounts.js
11795
+ function modelNames(modelId) {
11796
+ const value = modelId.trim();
11797
+ const names = /* @__PURE__ */ new Set([value]);
11798
+ for (const prefix of HOST_PREFIXES) {
11799
+ if (!value.startsWith(prefix))
11800
+ continue;
11801
+ const bare = value.slice(prefix.length);
11802
+ names.add(bare);
11803
+ const slash = bare.lastIndexOf("/");
11804
+ if (slash >= 0)
11805
+ names.add(bare.slice(slash + 1));
11806
+ }
11807
+ const canonical = canonicalModelId(value);
11808
+ if (canonical) {
11809
+ names.add(canonical);
11810
+ const entry = API_MODEL_CATALOG.find((m) => m.id === canonical || m.apiModel === canonical);
11811
+ if (entry) {
11812
+ names.add(entry.id);
11813
+ if (entry.apiModel)
11814
+ names.add(entry.apiModel);
11815
+ }
11816
+ }
11817
+ return names;
11818
+ }
11819
+ function accountServesModel(models, modelId) {
11820
+ if (!models || models.length === 0)
11821
+ return true;
11822
+ if (!modelId)
11823
+ return false;
11824
+ const names = modelNames(modelId);
11825
+ return models.some((model) => names.has(model.trim()));
11826
+ }
11827
+ function normalizeProviderAccountModels(models) {
11828
+ if (!Array.isArray(models))
11829
+ return null;
11830
+ const out = [];
11831
+ for (const model of models) {
11832
+ if (typeof model !== "string")
11833
+ return null;
11834
+ const trimmed = model.trim();
11835
+ if (!PROVIDER_ACCOUNT_MODEL_PATTERN.test(trimmed) || trimmed.includes(".."))
11836
+ return null;
11837
+ if (!out.includes(trimmed))
11838
+ out.push(trimmed);
11839
+ }
11840
+ return out.length > MAX_PROVIDER_ACCOUNT_MODELS ? null : out;
11841
+ }
11842
+ function parseProviderAccountLists(value, providers) {
11843
+ if (!value || typeof value !== "object" || Array.isArray(value))
11844
+ return null;
11845
+ const parsed = {};
11846
+ for (const provider of providers) {
11847
+ const list = value[provider];
11848
+ if (!Array.isArray(list))
11849
+ continue;
11850
+ const accounts = [];
11851
+ for (const raw of list) {
11852
+ if (!raw || typeof raw !== "object")
11853
+ continue;
11854
+ const { id, label, key, models } = raw;
11855
+ if (typeof id !== "string" || !id.trim() || typeof key !== "string" || !key.trim())
11856
+ continue;
11857
+ const modelList = Array.isArray(models) ? models.filter((m) => typeof m === "string" && m.trim() !== "").map((m) => m.trim()) : [];
11858
+ accounts.push({ id: id.trim(), label: typeof label === "string" && label.trim() ? label.trim() : null, key: key.trim(), models: modelList });
11859
+ }
11860
+ if (accounts.length > 0)
11861
+ parsed[provider] = accounts;
11862
+ }
11863
+ return parsed;
11864
+ }
11865
+ var DEFAULT_PROVIDER_ACCOUNT_ID, PROVIDER_ACCOUNT_ID_PATTERN, MAX_PROVIDER_ACCOUNTS, MAX_PROVIDER_ACCOUNT_MODELS, PROVIDER_ACCOUNT_MODEL_PATTERN, OPENCODE_GO_DEEPSEEK_GLM_MODELS, HOST_PREFIXES;
11866
+ var init_provider_accounts = __esm({
11867
+ "../shared/dist/provider-accounts.js"() {
11868
+ "use strict";
11869
+ init_api_model_catalog();
11870
+ DEFAULT_PROVIDER_ACCOUNT_ID = "primary";
11871
+ PROVIDER_ACCOUNT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,39}$/;
11872
+ MAX_PROVIDER_ACCOUNTS = 10;
11873
+ MAX_PROVIDER_ACCOUNT_MODELS = 50;
11874
+ PROVIDER_ACCOUNT_MODEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,120}$/;
11875
+ OPENCODE_GO_DEEPSEEK_GLM_MODELS = [
11876
+ "deepseek-v4.1-flash",
11877
+ "deepseek-v4-pro",
11878
+ "glm-5.3-flash",
11879
+ "glm-5.3",
11880
+ "glm-5.2"
11881
+ ];
11882
+ HOST_PREFIXES = ["opencode-go:", "zai:", "openrouter:"];
11205
11883
  }
11206
11884
  });
11207
11885
 
@@ -11320,6 +11998,25 @@ var init_project_engine_overrides = __esm({
11320
11998
  }
11321
11999
  });
11322
12000
 
12001
+ // ../shared/dist/trailing-slashes.js
12002
+ function stripTrailingSlashes(value) {
12003
+ let end = value.length;
12004
+ while (end > 0 && value.charCodeAt(end - 1) === 47)
12005
+ end--;
12006
+ return end === value.length ? value : value.slice(0, end);
12007
+ }
12008
+ function stripTrailingChars(value, isTrailing) {
12009
+ let end = value.length;
12010
+ while (end > 0 && isTrailing.test(value[end - 1]))
12011
+ end--;
12012
+ return end === value.length ? value : value.slice(0, end);
12013
+ }
12014
+ var init_trailing_slashes = __esm({
12015
+ "../shared/dist/trailing-slashes.js"() {
12016
+ "use strict";
12017
+ }
12018
+ });
12019
+
11323
12020
  // ../shared/dist/gemini-visual.js
11324
12021
  function visualProDailyBudget(raw) {
11325
12022
  const parsed = raw && /^\s*\d{1,6}\s*$/.test(raw) ? Number(raw.trim()) : NaN;
@@ -11333,7 +12030,7 @@ function isRetiredGeminiModel(id) {
11333
12030
  }
11334
12031
  function geminiNativeModelsUrl(base2 = process.env.GEMINI_API_BASE) {
11335
12032
  const raw = base2 && base2.trim() || GEMINI_OPENAI_BASE;
11336
- const native = raw.replace(/\/openai\/?$/, "/").replace(/\/+$/, "");
12033
+ const native = stripTrailingSlashes(raw.replace(/\/openai\/?$/, "/"));
11337
12034
  return `${native}/models?pageSize=1000`;
11338
12035
  }
11339
12036
  function asRecord(value) {
@@ -11453,6 +12150,7 @@ var KNOWN_INPUT_USD_PER_M, GEMINI_OPENAI_BASE, DEFAULT_GEMINI_SCREEN_MODEL, DEFA
11453
12150
  var init_gemini_visual = __esm({
11454
12151
  "../shared/dist/gemini-visual.js"() {
11455
12152
  "use strict";
12153
+ init_trailing_slashes();
11456
12154
  KNOWN_INPUT_USD_PER_M = {
11457
12155
  "gemini-2.5-flash-lite": 0.1,
11458
12156
  "gemini-3.1-flash-lite": 0.25,
@@ -12816,12 +13514,13 @@ function truncateAtWord(text, max) {
12816
13514
  const slice = trimmed.slice(0, room + 1);
12817
13515
  const lastSpace = slice.lastIndexOf(" ");
12818
13516
  const cut = lastSpace > room * 0.5 ? slice.slice(0, lastSpace) : trimmed.slice(0, room);
12819
- return `${cut.replace(/[\s,;:.\-–—]+$/u, "")}\u2026`;
13517
+ return `${stripTrailingChars(cut, TRAILING_PUNCTUATION)}\u2026`;
12820
13518
  }
12821
- var CANVAS_UNSUPPORTED_CODE, CANVAS_UNSUPPORTED_TITLE, CANVAS_UNSUPPORTED_BODY, CANVAS_UNSUPPORTED_NOT_CHARGED, CANVAS_UNSUPPORTED_OVERRIDE_HINT, GAME_ENGINES, RENDERER_ENGINES, ENGINE_TEXT_SIGNATURES, GAME_LIBRARY_TEXT_PATTERNS, ENGINE_PACKAGES, GAME_HINT_PACKAGES, MAX_TAG_CHARS, MAX_CSS_SELECTOR_CHARS, MAX_CSS_BLOCK_CHARS, TAG_NAME_RE, ATTR_RE, INTERACTIVE_TAGS, INTERACTIVE_ROLES, ROOT_SELECTOR_RE, OVERFLOW_HIDDEN_RE, TOUCH_NONE_RE, CODE_TAGS, STATIC_STANDARD_MIN_INTERACTIVE;
13519
+ var CANVAS_UNSUPPORTED_CODE, CANVAS_UNSUPPORTED_TITLE, CANVAS_UNSUPPORTED_BODY, CANVAS_UNSUPPORTED_NOT_CHARGED, CANVAS_UNSUPPORTED_OVERRIDE_HINT, GAME_ENGINES, RENDERER_ENGINES, ENGINE_TEXT_SIGNATURES, GAME_LIBRARY_TEXT_PATTERNS, ENGINE_PACKAGES, GAME_HINT_PACKAGES, MAX_TAG_CHARS, MAX_CSS_SELECTOR_CHARS, MAX_CSS_BLOCK_CHARS, TAG_NAME_RE, ATTR_RE, INTERACTIVE_TAGS, INTERACTIVE_ROLES, ROOT_SELECTOR_RE, OVERFLOW_HIDDEN_RE, TOUCH_NONE_RE, CODE_TAGS, STATIC_STANDARD_MIN_INTERACTIVE, TRAILING_PUNCTUATION;
12822
13520
  var init_canvas_support = __esm({
12823
13521
  "../shared/dist/canvas-support.js"() {
12824
13522
  "use strict";
13523
+ init_trailing_slashes();
12825
13524
  CANVAS_UNSUPPORTED_CODE = "canvas_unsupported";
12826
13525
  CANVAS_UNSUPPORTED_TITLE = "Games and canvas apps aren't supported yet";
12827
13526
  CANVAS_UNSUPPORTED_BODY = "This app draws its screen on a canvas (like a game), so there are no page elements for Validate.QA to read and test. Support for games and canvas apps is coming.";
@@ -12946,6 +13645,7 @@ var init_canvas_support = __esm({
12946
13645
  TOUCH_NONE_RE = /touch-action\s{0,8}:\s{0,8}none/;
12947
13646
  CODE_TAGS = /* @__PURE__ */ new Set(["script", "link", "flt-glass-pane", "flutter-view", "flt-scene-host"]);
12948
13647
  STATIC_STANDARD_MIN_INTERACTIVE = 3;
13648
+ TRAILING_PUNCTUATION = /[\s,;:.\-–—]/u;
12949
13649
  }
12950
13650
  });
12951
13651
 
@@ -13128,22 +13828,22 @@ function joinChainedLines(code) {
13128
13828
  const trimmed = lines[i].trimStart();
13129
13829
  if (last !== -1 && trimmed.startsWith(".") && !trimmed.startsWith("...")) {
13130
13830
  if (!lastStripped) {
13131
- lines[last] = stripLineComment2(lines[last]).trimEnd();
13831
+ lines[last] = stripLineComment3(lines[last]).trimEnd();
13132
13832
  lastStripped = true;
13133
13833
  }
13134
13834
  if (lines[last].length <= MAX_LINE_CHARS)
13135
- lines[last] = `${lines[last]} ${stripLineComment2(trimmed).trimEnd()}`;
13835
+ lines[last] = `${lines[last]} ${stripLineComment3(trimmed).trimEnd()}`;
13136
13836
  lines[i] = "";
13137
13837
  continue;
13138
13838
  }
13139
- if (stripLineComment2(lines[i]).trim()) {
13839
+ if (stripLineComment3(lines[i]).trim()) {
13140
13840
  last = i;
13141
13841
  lastStripped = false;
13142
13842
  }
13143
13843
  }
13144
13844
  return lines.join("\n");
13145
13845
  }
13146
- function stripLineComment2(rawLine) {
13846
+ function stripLineComment3(rawLine) {
13147
13847
  if (rawLine.trimStart().startsWith("//"))
13148
13848
  return "";
13149
13849
  const at = rawLine.indexOf(" // ");
@@ -13192,7 +13892,7 @@ function analyzeReadinessPolling(rawCode) {
13192
13892
  offset += rawLine.length + 1;
13193
13893
  if (rawLine.length > MAX_LINE_CHARS)
13194
13894
  continue;
13195
- const line = stripLineComment2(rawLine);
13895
+ const line = stripLineComment3(rawLine);
13196
13896
  if (!line.trim())
13197
13897
  continue;
13198
13898
  const literals = literalsOf(line);
@@ -13304,7 +14004,7 @@ function blankBlockComments2(code) {
13304
14004
  from = close + 2;
13305
14005
  }
13306
14006
  }
13307
- function stripLineComment3(rawLine) {
14007
+ function stripLineComment4(rawLine) {
13308
14008
  if (rawLine.trimStart().startsWith("//"))
13309
14009
  return "";
13310
14010
  const at = rawLine.indexOf(" // ");
@@ -13315,7 +14015,7 @@ function logicalLines(code) {
13315
14015
  for (const raw of code.split("\n")) {
13316
14016
  if (raw.length > MAX_LINE_CHARS2)
13317
14017
  continue;
13318
- const line = stripLineComment3(raw).trimEnd();
14018
+ const line = stripLineComment4(raw).trimEnd();
13319
14019
  if (!line.trim())
13320
14020
  continue;
13321
14021
  const trimmed = line.trimStart();
@@ -13677,8 +14377,19 @@ function short4(text, max) {
13677
14377
  const flat = text.replace(/\s+/g, " ").trim();
13678
14378
  return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
13679
14379
  }
14380
+ function stripFractionZeros(s) {
14381
+ let end = s.length;
14382
+ while (end > 0 && s[end - 1] === "0")
14383
+ end--;
14384
+ if (end === s.length)
14385
+ return s;
14386
+ const dot = s.lastIndexOf(".");
14387
+ if (dot < 0)
14388
+ return s;
14389
+ return s.slice(0, end === dot + 1 ? dot : end);
14390
+ }
13680
14391
  function digitsOf(token) {
13681
- return token.replace(/[^\d.]/g, "").replace(/\.0+$|(\.\d*?)0+$/, "$1").replace(/^0+(?=\d)/, "");
14392
+ return stripFractionZeros(token.replace(/[^\d.]/g, "")).replace(/^0+(?=\d)/, "");
13682
14393
  }
13683
14394
  function computes(line) {
13684
14395
  const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX").replace(LITERAL_ARITH_RE, "N");
@@ -13896,7 +14607,7 @@ function findExpectCalls(code) {
13896
14607
  if (!subj)
13897
14608
  continue;
13898
14609
  const rest = code.slice(subj.end + 1, subj.end + 1 + 80);
13899
- const mm = /^\s*((?:\.\s*not)?\s*\.\s*(\w+))\s{0,20}\(/.exec(rest);
14610
+ const mm = /^\s*((?:\.\s*not\s*)?\.\s*(\w+))\s{0,20}\(/.exec(rest);
13900
14611
  if (!mm)
13901
14612
  continue;
13902
14613
  const argStart = subj.end + 1 + mm[0].length;
@@ -15846,6 +16557,28 @@ function startsWithRegexLiteral(text) {
15846
16557
  const t = text.trimStart();
15847
16558
  return t[0] === "/" && t[1] !== "/" && t[1] !== "*" && regexLiteralEnd4(t, 0) !== -1;
15848
16559
  }
16560
+ function isBareRegexLiteral(text) {
16561
+ const t = text.trimStart();
16562
+ if (t[0] !== "/" || t[1] === "/" || t[1] === "*")
16563
+ return false;
16564
+ const end = regexLiteralEnd4(t, 0);
16565
+ if (end === -1)
16566
+ return false;
16567
+ let i = end;
16568
+ while (i < t.length && /[a-z]/.test(t[i]))
16569
+ i++;
16570
+ while (i < t.length && /\s/.test(t[i]))
16571
+ i++;
16572
+ return i >= t.length || t[i] === ",";
16573
+ }
16574
+ function regexDeclaredNames(code) {
16575
+ const names = /* @__PURE__ */ new Set();
16576
+ const decl = /\b(?:const|let|var)\s{1,20}([A-Za-z_$][\w$]{0,80})\s{0,20}=\s{0,20}(?:\/(?![/*])|new\s{1,20}RegExp\b)/g;
16577
+ let m;
16578
+ while ((m = decl.exec(code)) !== null)
16579
+ names.add(m[1]);
16580
+ return names;
16581
+ }
15849
16582
  function afterFirstArg(args) {
15850
16583
  let depth = 0;
15851
16584
  let quote = null;
@@ -15883,6 +16616,7 @@ function analyzeRegexMatchers(rawCode) {
15883
16616
  let examined = 0;
15884
16617
  let line = 1;
15885
16618
  let lineAt2 = 0;
16619
+ let regexNames;
15886
16620
  while ((m = re.exec(code)) !== null && examined++ < MAX_CALLS && out.length < MAX_REGEX_MATCHER_FINDINGS) {
15887
16621
  const subj = readBalanced(code, m.index + m[0].length);
15888
16622
  if (!subj)
@@ -15894,10 +16628,18 @@ function analyzeRegexMatchers(rawCode) {
15894
16628
  if (!args)
15895
16629
  continue;
15896
16630
  const expected = mm[2] === "toHaveJSProperty" ? afterFirstArg(args.text) : args.text;
15897
- if (!startsWithRegexLiteral(expected))
15898
- continue;
15899
- if (mm[2] !== "toHaveJSProperty" && mm[2] !== "toContain" && (startsWithRegexLiteral(subj.text) || /^new\s+RegExp\b/.test(subj.text.trim())))
16631
+ if (!isBareRegexLiteral(expected))
15900
16632
  continue;
16633
+ if (mm[2] !== "toHaveJSProperty" && mm[2] !== "toContain") {
16634
+ const subject = subj.text.trim();
16635
+ if (startsWithRegexLiteral(subject) || /^new\s+RegExp\b/.test(subject))
16636
+ continue;
16637
+ if (/^[A-Za-z_$][\w$]{0,80}$/.test(subject)) {
16638
+ regexNames ??= regexDeclaredNames(code);
16639
+ if (regexNames.has(subject))
16640
+ continue;
16641
+ }
16642
+ }
15901
16643
  for (let i = code.indexOf("\n", lineAt2); i !== -1 && i < m.index; i = code.indexOf("\n", i + 1)) {
15902
16644
  line++;
15903
16645
  lineAt2 = i + 1;
@@ -15907,9 +16649,12 @@ function analyzeRegexMatchers(rawCode) {
15907
16649
  return out;
15908
16650
  }
15909
16651
  function formatRegexMatcherNote(findings) {
15910
- const lines = findings.map((f) => `Line ${f.line}: .${f.negated ? "not." : ""}${f.matcher}(\u2026 /regex/ \u2026) ${f.negated ? "always passes (a regex never equals the value), so it checks nothing" : "can never pass: this matcher compares by equality and a regex never equals a value"}.`);
16652
+ const lines = findings.map((f) => {
16653
+ const negated = f.matcher === "toContain" ? "throws on a string and otherwise always passes (a regex never equals an item), so it checks nothing" : "always passes (a regex never equals the value), so it checks nothing";
16654
+ return `Line ${f.line}: .${f.negated ? "not." : ""}${f.matcher}(\u2026 /regex/ \u2026) ${f.negated ? negated : "can never pass: this matcher compares by equality and a regex never equals a value"}.`;
16655
+ });
15911
16656
  return `${lines.join("\n")}
15912
- A regex only works in toMatch, toHaveText, toContainText, toHaveValue and similar text matchers. For a DOM property read it first: expect(await loc.evaluate((n) => n.<prop>)).toMatch(/re/). Or give toHaveJSProperty the exact string (or boolean) the page shows. Keep every other line as-is.`;
16657
+ A regex only works in toMatch, toHaveText, toContainText, toHaveValue and similar text matchers. For the \`value\` of an input, textarea or select, use await expect(loc).toHaveValue(/re/). For any other DOM property (or element), poll it so the check still retries: await expect.poll(() => loc.evaluate((n) => n.<prop>)).toMatch(/re/). Never read it once with expect(await loc.evaluate(\u2026)), which sees the value before the page updates it. Or give toHaveJSProperty the exact string (or boolean) the page shows. Keep every other line as-is.`;
15913
16658
  }
15914
16659
  function lintTestQuality(rawCode, opts = {}) {
15915
16660
  if (!rawCode || typeof rawCode !== "string")
@@ -16658,19 +17403,6 @@ var init_fence_runs = __esm({
16658
17403
  }
16659
17404
  });
16660
17405
 
16661
- // ../shared/dist/trailing-slashes.js
16662
- function stripTrailingSlashes(value) {
16663
- let end = value.length;
16664
- while (end > 0 && value.charCodeAt(end - 1) === 47)
16665
- end--;
16666
- return end === value.length ? value : value.slice(0, end);
16667
- }
16668
- var init_trailing_slashes = __esm({
16669
- "../shared/dist/trailing-slashes.js"() {
16670
- "use strict";
16671
- }
16672
- });
16673
-
16674
17406
  // ../shared/dist/survey-bug-signature.js
16675
17407
  function normalizeWords(text) {
16676
17408
  return text.toLowerCase().replace(UUID, " id ").replace(PREFIXED_ID, " id ").replace(HEX_RUN, (run) => /\d/.test(run) ? " id " : run).replace(NUMBER_RUN, " # ").replace(/[^\p{L}\p{N}#]+/gu, " ").replace(/\s+/g, " ").trim();
@@ -16694,14 +17426,17 @@ function requestSignatures(text) {
16694
17426
  const statuses = [...text.matchAll(HTTP_STATUS)].map((m) => ({ status: m[1], at: m.index ?? 0, end: (m.index ?? 0) + m[0].length }));
16695
17427
  if (statuses.length === 0)
16696
17428
  return [];
16697
- const paths = [...text.matchAll(PATH_IN_TEXT)].map((m) => ({
16698
- method: m[1] ?? null,
16699
- path: normalizeBugPath((m[2] ?? m[3] ?? "").replace(/[.,;:]+$/, "")),
16700
- // Before normalisation, which turns a segment with a digit (favicon-32x32.png) into :id.
16701
- incidental: isIncidentalAssetPath((m[2] ?? m[3] ?? "").replace(/[.,;:]+$/, "").split(/[?#]/, 1)[0].toLowerCase()),
16702
- at: m.index ?? 0,
16703
- end: (m.index ?? 0) + m[0].length
16704
- })).filter((p) => p.path.length > 1);
17429
+ const paths = [...text.matchAll(PATH_IN_TEXT)].map((m) => {
17430
+ const rawPath = stripTrailingChars(m[2] ?? m[3] ?? "", PATH_TRAILING_PUNCTUATION);
17431
+ return {
17432
+ method: m[1] ?? null,
17433
+ path: normalizeBugPath(rawPath),
17434
+ // Before normalisation, which turns a segment with a digit (favicon-32x32.png) into :id.
17435
+ incidental: isIncidentalAssetPath(rawPath.split(/[?#]/, 1)[0].toLowerCase()),
17436
+ at: m.index ?? 0,
17437
+ end: (m.index ?? 0) + m[0].length
17438
+ };
17439
+ }).filter((p) => p.path.length > 1);
16705
17440
  const out = /* @__PURE__ */ new Map();
16706
17441
  for (const { status, at, end } of statuses) {
16707
17442
  const lead = STATUS_TO_PATH.exec(text.slice(end, end + 40));
@@ -16811,11 +17546,12 @@ function dedupeSiteWideSuspectedBugs(drafts) {
16811
17546
  delete plans.suspectedBugs;
16812
17547
  }
16813
17548
  }
16814
- var SIGNATURE_SOURCE_MAX_CHARS, UUID, PREFIXED_ID, HEX_RUN, NUMBER_RUN, GENERIC_JS_MESSAGES, GENERIC_JS_PREFIXES, JS_ERROR, MESSAGE_CLAUSE_END, HTTP_STATUS, PATH_IN_TEXT, STATUS_TO_PATH, INCIDENTAL_ASSET;
17549
+ var PATH_TRAILING_PUNCTUATION, SIGNATURE_SOURCE_MAX_CHARS, UUID, PREFIXED_ID, HEX_RUN, NUMBER_RUN, GENERIC_JS_MESSAGES, GENERIC_JS_PREFIXES, JS_ERROR, MESSAGE_CLAUSE_END, HTTP_STATUS, PATH_IN_TEXT, STATUS_TO_PATH, INCIDENTAL_ASSET;
16815
17550
  var init_survey_bug_signature = __esm({
16816
17551
  "../shared/dist/survey-bug-signature.js"() {
16817
17552
  "use strict";
16818
17553
  init_trailing_slashes();
17554
+ PATH_TRAILING_PUNCTUATION = /[.,;:]/;
16819
17555
  SIGNATURE_SOURCE_MAX_CHARS = 800;
16820
17556
  UUID = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g;
16821
17557
  PREFIXED_ID = /\b[a-z]{1,8}[-_]\d+\b/g;
@@ -18146,7 +18882,7 @@ function pathIdsIn(text) {
18146
18882
  function findCounterIdPins(code) {
18147
18883
  if (code.length > CLOCK_MAX_CODE_CHARS)
18148
18884
  return [];
18149
- const clean2 = stripLineComments(blankBlockComments3(code));
18885
+ const clean3 = stripLineComments(blankBlockComments3(code));
18150
18886
  const fillValues = [];
18151
18887
  const candidates = [];
18152
18888
  let seenWrite = false;
@@ -18154,24 +18890,24 @@ function findCounterIdPins(code) {
18154
18890
  CALL_RE.lastIndex = 0;
18155
18891
  let m;
18156
18892
  let calls = 0;
18157
- while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean2)) !== null) {
18893
+ while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean3)) !== null) {
18158
18894
  const name = m[1];
18159
18895
  const open = m.index + m[0].length - 1;
18160
18896
  if (VALUE_WRITES.has(name)) {
18161
18897
  seenWrite = true;
18162
- const args2 = argsAt(clean2, open);
18898
+ const args2 = argsAt(clean3, open);
18163
18899
  if (args2 !== null)
18164
18900
  fillValues.push(...staticStrings(args2));
18165
18901
  continue;
18166
18902
  }
18167
18903
  if (createIndex === -1) {
18168
- if (name === "click" && seenWrite && isCreateClick(clean2, m.index))
18904
+ if (name === "click" && seenWrite && isCreateClick(clean3, m.index))
18169
18905
  createIndex = m.index;
18170
18906
  continue;
18171
18907
  }
18172
18908
  if (name === "click")
18173
18909
  continue;
18174
- const args = argsAt(clean2, open);
18910
+ const args = argsAt(clean3, open);
18175
18911
  if (args === null)
18176
18912
  continue;
18177
18913
  const isUrl = URL_SINKS.has(name);
@@ -18190,7 +18926,7 @@ function findCounterIdPins(code) {
18190
18926
  }
18191
18927
  if (createIndex === -1)
18192
18928
  return [];
18193
- const before = clean2.slice(0, createIndex);
18929
+ const before = clean3.slice(0, createIndex);
18194
18930
  let pathIdsBefore = null;
18195
18931
  const findings = [];
18196
18932
  const seen = /* @__PURE__ */ new Set();
@@ -18214,7 +18950,7 @@ function findCounterIdPins(code) {
18214
18950
  continue;
18215
18951
  }
18216
18952
  }
18217
- findings.push({ kind: c.kind, literal: c.literal, line: lineAt(clean2, c.index) });
18953
+ findings.push({ kind: c.kind, literal: c.literal, line: lineAt(clean3, c.index) });
18218
18954
  }
18219
18955
  return findings;
18220
18956
  }
@@ -18509,7 +19245,7 @@ var init_suspicious_expectation = __esm({
18509
19245
  NOT_STUCK_RE = /\b(?:not|never|no longer|isn't|doesn't|don't|without)\s+(?:\w+\s+){0,2}?(?:stuck|hang|hangs|hung|freeze|freezes|frozen|spin|spins|spinning)\b/i;
18510
19246
  LISTS_STUCK_RE = /\b(?:appears? in (?:the )?[\w ]{0,25}(?:report|list|dashboard|queue)|(?:lists?|monitors?|shows?|reports?) (?:\w+ ){0,2}(?:that|which|whose) (?:are |is )?(?:stuck|hangs?|hung|frozen)|can (?:retry|re-?run) (?:\w+ ){0,2}(?:that (?:are )?)?stuck)\b/i;
18511
19247
  STUCK_VETO_RE = new RegExp(`${NOT_STUCK_RE.source}|${LISTS_STUCK_RE.source}`, "i");
18512
- NOT_SILENT_RE = /\b(?:not|never|\w+n't|instead of|rather than|avoids?|prevents?)\s+(?:\w+\s+){0,1}?(?:fail(?:s|ing|ed)?|ignor\w*)?\s*silent(?:ly)?\b/i;
19248
+ NOT_SILENT_RE = /\b(?:not|never|\w+n't|instead of|rather than|avoids?|prevents?)\s+(?:\w+\s+){0,1}?(?:(?:fail(?:s|ing|ed)?|ignor\w*)\s*)?silent(?:ly)?\b/i;
18513
19249
  PENDING_WORD = "(?:sending|loading|saving|searching|checking(?!\\s+(?:account|balance|out|in)\\b)|syncing|refreshing|fetching|validating|processing|submitting|uploading|posting|publishing|creating|updating|deleting|connecting|verifying|signing in|logging in)";
18514
19250
  ELLIPSIS = "(?:\\.{3}|\u2026)";
18515
19251
  NOT_STAY_RE = /\b(?:not|never|no longer|without|n't|cannot)\s+(?:\w+\s+){0,2}?(?:stay|stays|stayed|remain|remains|remained|leave|leaves|left)\b/i;
@@ -18665,7 +19401,7 @@ function outcomeNamedByAssertion(outcome, code) {
18665
19401
  function namedByStatements(outcome, statements) {
18666
19402
  const text = outcomeText(outcome);
18667
19403
  if (outcome.type === "url") {
18668
- const routes = (text.match(ROUTE_TERM) ?? []).map((r) => r.replace(/[.,;:!?)]+$/, "")).filter((r) => r.length > 1);
19404
+ const routes = (text.match(ROUTE_TERM) ?? []).map((r) => stripTrailingChars(r, ROUTE_TRAILING_PUNCTUATION)).filter((r) => r.length > 1);
18669
19405
  return statements.some((stmt) => {
18670
19406
  if (!/\.(?:waitForURL|toHaveURL)\s{0,20}\(/.test(stmt))
18671
19407
  return false;
@@ -18837,7 +19573,7 @@ function evaluatePromotionGate(input, checks = PROMOTION_QUALITY_CHECKS) {
18837
19573
  ...suspiciousTagged ? { suspiciousPass: { mode: suspicious, covered: suspiciousCovered, wouldHold: suspiciousHold } } : {}
18838
19574
  };
18839
19575
  }
18840
- var SOURCE_RANK, strengthCheck, STRING_CONST, TYPED_VALUE, TYPED_CONSTANT, MAX_STATEMENTS, MAX_STATEMENT_CHARS, LITERAL_OR_IDENTIFIER, MUTATING_ACTION, FEEDBACK_REGION, ONE_SHOT_MESSAGE_REASON, FEEDBACK_REGION_ALL, dynamicAssertionCheck, OUTCOME_KIND_MATCHER, ROUTE_TERM, QUOTED_LITERAL, GENERIC_LITERAL, MAX_OUTCOME_STATEMENTS, MAX_OUTCOMES, MAX_OUTCOME_TEXT_CHARS, expectedOutcomeCheck, testDataCheck, sharedAccountCheck, clockLiteralCheck, readinessPollCheck, knownBugTestCheck, bugClaimContractCheck, suspiciousExpectationCheck, bugCandidateCheck, conventionOutcomesCheck, pendingHook, locatorFragmentCheck, FILTER_STEP, NEXT_ACTION, REDUCTION_PROOF, MAX_FILTER_LINES, MAX_FILTER_LINE_CHARS, filterPositiveOnlyCheck, PROMOTION_QUALITY_CHECKS, PROMOTION_GATE_MAX_CODE_CHARS, NOT_EVALUATED, SUSPICIOUS_PASS_TAG, NOT_CONTENT_MATCHER, CONTENT_PROOF, NAMED_ELEMENT, NEEDS_REVIEW_TAG, count2, RECHECKED_NOTES, EVIDENCE_NOTE_PREFIXES, PENDING_RECHECK_NOTE_PREFIXES, STRENGTH_RANK;
19576
+ var SOURCE_RANK, strengthCheck, STRING_CONST, TYPED_VALUE, TYPED_CONSTANT, MAX_STATEMENTS, MAX_STATEMENT_CHARS, LITERAL_OR_IDENTIFIER, MUTATING_ACTION, FEEDBACK_REGION, ONE_SHOT_MESSAGE_REASON, FEEDBACK_REGION_ALL, dynamicAssertionCheck, OUTCOME_KIND_MATCHER, ROUTE_TERM, ROUTE_TRAILING_PUNCTUATION, QUOTED_LITERAL, GENERIC_LITERAL, MAX_OUTCOME_STATEMENTS, MAX_OUTCOMES, MAX_OUTCOME_TEXT_CHARS, expectedOutcomeCheck, testDataCheck, sharedAccountCheck, clockLiteralCheck, readinessPollCheck, knownBugTestCheck, bugClaimContractCheck, suspiciousExpectationCheck, bugCandidateCheck, conventionOutcomesCheck, pendingHook, locatorFragmentCheck, FILTER_STEP, NEXT_ACTION, REDUCTION_PROOF, MAX_FILTER_LINES, MAX_FILTER_LINE_CHARS, filterPositiveOnlyCheck, PROMOTION_QUALITY_CHECKS, PROMOTION_GATE_MAX_CODE_CHARS, NOT_EVALUATED, SUSPICIOUS_PASS_TAG, NOT_CONTENT_MATCHER, CONTENT_PROOF, NAMED_ELEMENT, NEEDS_REVIEW_TAG, count2, RECHECKED_NOTES, EVIDENCE_NOTE_PREFIXES, PENDING_RECHECK_NOTE_PREFIXES, STRENGTH_RANK;
18841
19577
  var init_promotion_quality_gate = __esm({
18842
19578
  "../shared/dist/promotion-quality-gate.js"() {
18843
19579
  "use strict";
@@ -18852,11 +19588,12 @@ var init_promotion_quality_gate = __esm({
18852
19588
  init_run_classification();
18853
19589
  init_expected_outcome_coverage();
18854
19590
  init_expect_call();
19591
+ init_trailing_slashes();
18855
19592
  SOURCE_RANK = { generator: 0, classifier: 0, healer: 1, human: 2 };
18856
19593
  strengthCheck = {
18857
19594
  id: "assertion-strength",
18858
- run({ code, tags, strengthGate, flagContradictedAdditions }) {
18859
- const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true });
19595
+ run({ code, tags, strengthGate, flagContradictedAdditions, lastActionProof }) {
19596
+ const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true, lastActionProof });
18860
19597
  if (verdict.severity === "route-bug")
18861
19598
  return [{ check: "route-bug" }];
18862
19599
  const authDowngrade = verdict.severity === "downgrade" && tags.some((t) => t.toLowerCase() === "@auth");
@@ -18874,7 +19611,7 @@ var init_promotion_quality_gate = __esm({
18874
19611
  MAX_STATEMENTS = 500;
18875
19612
  MAX_STATEMENT_CHARS = 400;
18876
19613
  LITERAL_OR_IDENTIFIER = /'(?:[^'\\\n]|\\.){0,500}'|"(?:[^"\\\n]|\\.){0,500}"|`(?:[^`\\]|\\.){0,2000}`|(?<![\w$.])[A-Za-z_$][\w$]{0,60}/g;
18877
- MUTATING_ACTION = /\.(?:click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|selectOption|setInputFiles|dragTo|clear)\s{0,20}\(|\brequest\s{0,20}\.\s{0,20}(?:post|put|patch|delete|fetch)\s{0,20}\(/;
19614
+ MUTATING_ACTION = /\.(?:click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|setChecked|selectOption|setInputFiles|dragTo|dragAndDrop|clear)\s{0,20}\(|\brequest\s{0,20}\.\s{0,20}(?:post|put|patch|delete|fetch)\s{0,20}\(/;
18878
19615
  FEEDBACK_REGION = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/i;
18879
19616
  ONE_SHOT_MESSAGE_REASON = "one-shot success/error banner text";
18880
19617
  FEEDBACK_REGION_ALL = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/gi;
@@ -18933,6 +19670,7 @@ var init_promotion_quality_gate = __esm({
18933
19670
  "element-hidden": /\.(?:toBeHidden|not\s{0,20}\.\s{0,20}toBeVisible|not\s{0,20}\.\s{0,20}toBeAttached|not\s{0,20}\.\s{0,20}toContainText)\s{0,20}\(|\.toHaveCount\s{0,20}\(\s{0,20}0\s{0,20}\)/
18934
19671
  };
18935
19672
  ROUTE_TERM = /\/[A-Za-z0-9_./?&=#-]{1,200}/g;
19673
+ ROUTE_TRAILING_PUNCTUATION = /[.,;:!?)]/;
18936
19674
  QUOTED_LITERAL = /'([^'\\\n]{3,200})'|"([^"\\\n]{3,200})"|`([^`\\$\n]{3,200})`/g;
18937
19675
  GENERIC_LITERAL = /^(?:true|false|button|link|heading|dialog|alertdialog|article|main|region|status|alert|textbox|combobox|checkbox|radio|tab|tabpanel|option|list|listitem|row|cell|img|navigation|banner|complementary|contentinfo|form|search|menu|menuitem|visible|hidden|attached|detached|domcontentloaded|load|networkidle)$/i;
18938
19676
  MAX_OUTCOME_STATEMENTS = 300;
@@ -19052,8 +19790,8 @@ ${goalText}`);
19052
19790
  return count3 > 0 ? [{ check: "locator-fragment", detail: String(count3), soft: true }] : [];
19053
19791
  }
19054
19792
  };
19055
- FILTER_STEP = /\.(?:getByLabel|getByPlaceholder|getByRole|getByTitle)\s*\([^)\n]{0,200}?\b(?:filter|sort|search|status|delete|remove|dismiss|archive|clear)\b[^)\n]{0,200}\)[^\n]{0,200}\.(?:selectOption|fill|type|pressSequentially|click|check|uncheck|press)\s*\(/i;
19056
- NEXT_ACTION = /\.(?:goto|reload|click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|selectOption|setInputFiles|dragTo|clear)\s*\(/;
19793
+ FILTER_STEP = /\.(?:getByLabel|getByPlaceholder|getByRole|getByTitle)\s*\([^)\n]{0,200}?\b(?:filter|sort|search|status|delete|remove|dismiss|archive|clear)\b[^)\n]{0,200}\)[^\n]{0,200}\.(?:selectOption|fill|type|pressSequentially|click|check|uncheck|setChecked|press)\s*\(/i;
19794
+ NEXT_ACTION = /\.(?:goto|reload|click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|setChecked|selectOption|setInputFiles|dragTo|dragAndDrop|clear)\s*\(/;
19057
19795
  REDUCTION_PROOF = /\.toHaveCount\s*\(|\.not\s*\.\s*(?:toBeVisible|toBeAttached|toContainText|toHaveText)\s*\(|\.toBeHidden\s*\(|\bShowing\b|\bof \d+\b|\b\d+ (?:results?|rows?|items?|candidates?|entries|matches|records)\b|\bNo (?:[\w'-]+ ){0,3}(?:match|matches|results?|found|left|remain|yet)\b|\bNothing (?:to show|found|matches)\b|\bis empty\b|\btoHaveURL\b/i;
19058
19796
  MAX_FILTER_LINES = 2e3;
19059
19797
  MAX_FILTER_LINE_CHARS = 600;
@@ -19168,7 +19906,7 @@ ${goalText}`);
19168
19906
  });
19169
19907
 
19170
19908
  // ../shared/dist/regex-alternation.js
19171
- function closingParens(source) {
19909
+ function closingParens2(source) {
19172
19910
  const close = new Int32Array(source.length).fill(-1);
19173
19911
  const open = [];
19174
19912
  let inClass = false;
@@ -19193,7 +19931,7 @@ function closingParens(source) {
19193
19931
  return close;
19194
19932
  }
19195
19933
  function alternatesAtTop(source) {
19196
- const close = closingParens(source);
19934
+ const close = closingParens2(source);
19197
19935
  let start = source.startsWith("^") ? 1 : 0;
19198
19936
  let end = source.endsWith("$") ? source.length - 1 : source.length;
19199
19937
  while (start < end && source[start] === "(" && close[start] === end - 1) {
@@ -19242,6 +19980,124 @@ var init_page_evidence = __esm({
19242
19980
  }
19243
19981
  });
19244
19982
 
19983
+ // ../shared/dist/stated-limits.js
19984
+ function validDate(v) {
19985
+ const [y, m, d] = v.split("-").map(Number);
19986
+ const t = new Date(Date.UTC(y, m - 1, d));
19987
+ return t.getUTCFullYear() === y && t.getUTCMonth() === m - 1 && t.getUTCDate() === d;
19988
+ }
19989
+ function isoFor(type, v) {
19990
+ const re = DATE_RE[type];
19991
+ if (!re || !re.test(v))
19992
+ return false;
19993
+ if (type === "date")
19994
+ return validDate(v);
19995
+ if (type === "datetime-local")
19996
+ return validDate(v.slice(0, 10)) && Number(v.slice(11, 13)) < 24 && Number(v.slice(14, 16)) < 60;
19997
+ if (type === "time")
19998
+ return Number(v.slice(0, 2)) < 24 && Number(v.slice(3, 5)) < 60;
19999
+ if (type === "month")
20000
+ return Number(v.slice(5, 7)) >= 1 && Number(v.slice(5, 7)) <= 12;
20001
+ return Number(v.slice(6, 8)) >= 1 && Number(v.slice(6, 8)) <= 53;
20002
+ }
20003
+ function clean2(raw) {
20004
+ return typeof raw === "string" ? raw.trim() : "";
20005
+ }
20006
+ function statedLimitsFromAttrs(attrs, inputType) {
20007
+ const type = (inputType ?? "").trim().toLowerCase();
20008
+ const limits = [];
20009
+ const add = (k, v) => {
20010
+ limits.push({ k, v });
20011
+ };
20012
+ if (NUMBER_TYPES.has(type)) {
20013
+ for (const [attr, k] of [["min", "min"], ["max", "max"]]) {
20014
+ const v = clean2(attrs[attr]);
20015
+ if (NUMBER_RE.test(v))
20016
+ add(k, v);
20017
+ }
20018
+ const step = clean2(attrs.step);
20019
+ if (NUMBER_RE.test(step) && Number(step) > 0)
20020
+ add("step", step);
20021
+ } else if (type in DATE_RE) {
20022
+ for (const [attr, k] of [["min", "min"], ["max", "max"]]) {
20023
+ const v = clean2(attrs[attr]);
20024
+ if (isoFor(type, v))
20025
+ add(k, v);
20026
+ }
20027
+ } else if (TEXT_TYPES.has(type)) {
20028
+ for (const [attr, k] of [["minlength", "minLength"], ["maxlength", "maxLength"]]) {
20029
+ const v = clean2(attrs[attr]);
20030
+ if (LENGTH_RE.test(v))
20031
+ add(k, String(Number(v)));
20032
+ }
20033
+ const pattern = cleanPattern(clean2(attrs.pattern).slice(0, STATED_PATTERN_MAX));
20034
+ if (pattern)
20035
+ add("pattern", pattern);
20036
+ }
20037
+ return limits.length > 0 ? { v: STATED_LIMITS_VERSION, limits } : null;
20038
+ }
20039
+ function cleanPattern(text) {
20040
+ return text.replace(/[\u0000-\u001f\u007f]/g, "").replace(/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g, "");
20041
+ }
20042
+ function readStatedLimits(stored) {
20043
+ if (!stored || typeof stored !== "object" || Array.isArray(stored))
20044
+ return [];
20045
+ const rec = stored;
20046
+ if (rec.v !== STATED_LIMITS_VERSION || !Array.isArray(rec.limits))
20047
+ return [];
20048
+ const out = [];
20049
+ const seen = /* @__PURE__ */ new Set();
20050
+ for (const item of rec.limits.slice(0, MAX_LIMITS * 2)) {
20051
+ if (!item || typeof item !== "object")
20052
+ continue;
20053
+ const { k, v } = item;
20054
+ if (typeof k !== "string" || typeof v !== "string" || !KINDS.includes(k) || seen.has(k))
20055
+ continue;
20056
+ if (v.length === 0 || v.length > STATED_PATTERN_MAX)
20057
+ continue;
20058
+ const text = k === "pattern" ? cleanPattern(v) : v;
20059
+ if (text === "")
20060
+ continue;
20061
+ if (k === "minLength" || k === "maxLength") {
20062
+ if (!LENGTH_RE.test(v))
20063
+ continue;
20064
+ } else if (k === "step") {
20065
+ if (!NUMBER_RE.test(v))
20066
+ continue;
20067
+ } else if (k === "min" || k === "max") {
20068
+ if (!NUMBER_RE.test(v) && !Object.values(DATE_RE).some((re) => re.test(v)))
20069
+ continue;
20070
+ }
20071
+ seen.add(k);
20072
+ out.push({ k, v: text });
20073
+ }
20074
+ return out;
20075
+ }
20076
+ function statedLimitsRecord(limits) {
20077
+ return limits.length > 0 ? { v: STATED_LIMITS_VERSION, limits } : null;
20078
+ }
20079
+ var STATED_LIMITS_VERSION, STATED_PATTERN_MAX, KINDS, MAX_LIMITS, TEXT_TYPES, NUMBER_TYPES, DATE_RE, NUMBER_RE, LENGTH_RE;
20080
+ var init_stated_limits = __esm({
20081
+ "../shared/dist/stated-limits.js"() {
20082
+ "use strict";
20083
+ STATED_LIMITS_VERSION = 1;
20084
+ STATED_PATTERN_MAX = 200;
20085
+ KINDS = ["min", "max", "minLength", "maxLength", "step", "pattern"];
20086
+ MAX_LIMITS = KINDS.length;
20087
+ TEXT_TYPES = /* @__PURE__ */ new Set(["text", "search", "email", "url", "tel", "password", "textarea", ""]);
20088
+ NUMBER_TYPES = /* @__PURE__ */ new Set(["number", "range"]);
20089
+ DATE_RE = {
20090
+ date: /^\d{4}-\d{2}-\d{2}$/,
20091
+ "datetime-local": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?$/,
20092
+ time: /^\d{2}:\d{2}(?::\d{2})?$/,
20093
+ month: /^\d{4}-\d{2}$/,
20094
+ week: /^\d{4}-W\d{2}$/
20095
+ };
20096
+ NUMBER_RE = /^-?\d{1,15}(?:\.\d{1,10})?$/;
20097
+ LENGTH_RE = /^\d{1,7}$/;
20098
+ }
20099
+ });
20100
+
19245
20101
  // ../shared/dist/index.js
19246
20102
  var dist_exports = {};
19247
20103
  __export(dist_exports, {
@@ -19249,9 +20105,11 @@ __export(dist_exports, {
19249
20105
  ACTION_BATCH_MAX_SIZE: () => ACTION_BATCH_MAX_SIZE,
19250
20106
  ADDON_DEFINITIONS: () => ADDON_DEFINITIONS,
19251
20107
  ADMIN_EMAIL_LIMITS: () => ADMIN_EMAIL_LIMITS,
20108
+ ADMIN_FORCE_CANCELLED_RUN_ERROR: () => ADMIN_FORCE_CANCELLED_RUN_ERROR,
19252
20109
  ADMIN_VIEW_DEFAULT_RETURN_TO: () => ADMIN_VIEW_DEFAULT_RETURN_TO,
19253
20110
  AGENT_AI_PROVIDER_PREFIXES: () => AGENT_AI_PROVIDER_PREFIXES,
19254
20111
  AI_CREDIT_MARGIN: () => AI_CREDIT_MARGIN,
20112
+ ALLOWED_PROVIDER_CHAIN_HOPS: () => ALLOWED_PROVIDER_CHAIN_HOPS,
19255
20113
  API_AUTH_HEADERS_ENV_KEY: () => API_AUTH_HEADERS_ENV_KEY,
19256
20114
  API_MODEL_CATALOG: () => API_MODEL_CATALOG,
19257
20115
  API_MODEL_FIELDS: () => API_MODEL_FIELDS,
@@ -19291,12 +20149,14 @@ __export(dist_exports, {
19291
20149
  BUG_TEST_BROKEN_REASON: () => BUG_TEST_BROKEN_REASON,
19292
20150
  BUG_TEST_BROKEN_TAG: () => BUG_TEST_BROKEN_TAG,
19293
20151
  CACHE_PRICE_PER_M: () => CACHE_PRICE_PER_M,
20152
+ CANCELLED_REASON_PREFIXES: () => CANCELLED_REASON_PREFIXES,
19294
20153
  CANVAS_UNSUPPORTED_BODY: () => CANVAS_UNSUPPORTED_BODY,
19295
20154
  CANVAS_UNSUPPORTED_CODE: () => CANVAS_UNSUPPORTED_CODE,
19296
20155
  CANVAS_UNSUPPORTED_NOT_CHARGED: () => CANVAS_UNSUPPORTED_NOT_CHARGED,
19297
20156
  CANVAS_UNSUPPORTED_OVERRIDE_HINT: () => CANVAS_UNSUPPORTED_OVERRIDE_HINT,
19298
20157
  CANVAS_UNSUPPORTED_TITLE: () => CANVAS_UNSUPPORTED_TITLE,
19299
20158
  CAPTURE_JSON_STRUCTURE_LIMITS: () => CAPTURE_JSON_STRUCTURE_LIMITS,
20159
+ CHAIN_CANCELLED_RUN_ERROR: () => CHAIN_CANCELLED_RUN_ERROR,
19300
20160
  CHAT_FAILOVER_KEY: () => CHAT_FAILOVER_KEY,
19301
20161
  CLAUDE_CLI_DEFAULT_EFFORT: () => CLAUDE_CLI_DEFAULT_EFFORT,
19302
20162
  CLAUDE_CLI_DEFAULT_MODEL: () => CLAUDE_CLI_DEFAULT_MODEL,
@@ -19333,6 +20193,7 @@ __export(dist_exports, {
19333
20193
  DEFAULT_GEMINI_SCREEN_MODEL: () => DEFAULT_GEMINI_SCREEN_MODEL,
19334
20194
  DEFAULT_JSON_STRUCTURE_LIMITS: () => DEFAULT_JSON_STRUCTURE_LIMITS,
19335
20195
  DEFAULT_PLATFORM_DB_POOL_SIZE: () => DEFAULT_PLATFORM_DB_POOL_SIZE,
20196
+ DEFAULT_PROVIDER_ACCOUNT_ID: () => DEFAULT_PROVIDER_ACCOUNT_ID,
19336
20197
  DEFAULT_SHARED_DB_POOL_SIZE: () => DEFAULT_SHARED_DB_POOL_SIZE,
19337
20198
  DEFAULT_STT_MODEL: () => DEFAULT_STT_MODEL,
19338
20199
  DEFAULT_STT_MODEL_BY_PROVIDER: () => DEFAULT_STT_MODEL_BY_PROVIDER,
@@ -19349,6 +20210,7 @@ __export(dist_exports, {
19349
20210
  ENVIRONMENTAL_ERROR_STORED_PREFIXES: () => ENVIRONMENTAL_ERROR_STORED_PREFIXES,
19350
20211
  EVIDENCE_NOTE_PREFIXES: () => EVIDENCE_NOTE_PREFIXES,
19351
20212
  EXPECT_CALL_RE: () => EXPECT_CALL_RE,
20213
+ FEATURE_ROW_RUN_WINDOW: () => FEATURE_ROW_RUN_WINDOW,
19352
20214
  FLOW_PHASE_LABELS: () => FLOW_PHASE_LABELS,
19353
20215
  FLOW_PHASE_TYPES: () => FLOW_PHASE_TYPES,
19354
20216
  FLOW_SERVER_TYPES: () => FLOW_SERVER_TYPES,
@@ -19416,6 +20278,8 @@ __export(dist_exports, {
19416
20278
  MAX_ORG_NAME_LENGTH: () => MAX_ORG_NAME_LENGTH,
19417
20279
  MAX_PASSWORD_BYTES: () => MAX_PASSWORD_BYTES,
19418
20280
  MAX_PLAYWRIGHT_CODE_LENGTH: () => MAX_PLAYWRIGHT_CODE_LENGTH,
20281
+ MAX_PROVIDER_ACCOUNTS: () => MAX_PROVIDER_ACCOUNTS,
20282
+ MAX_PROVIDER_ACCOUNT_MODELS: () => MAX_PROVIDER_ACCOUNT_MODELS,
19419
20283
  MAX_SURVEY_ENTRY_URLS: () => MAX_SURVEY_ENTRY_URLS,
19420
20284
  MAX_SURVEY_ENTRY_URL_LENGTH: () => MAX_SURVEY_ENTRY_URL_LENGTH,
19421
20285
  MCP_ARMY_LIMITS: () => MCP_ARMY_LIMITS,
@@ -19451,7 +20315,7 @@ __export(dist_exports, {
19451
20315
  MUSE_ENGINE_DEFAULT_MODEL: () => MUSE_ENGINE_DEFAULT_MODEL,
19452
20316
  MUSE_ENGINE_MODELS: () => MUSE_ENGINE_MODELS,
19453
20317
  NOREPLY_EMAIL: () => NOREPLY_EMAIL,
19454
- OPENCODE_GO_2_PREFIX: () => OPENCODE_GO_2_PREFIX,
20318
+ OPENCODE_GO_DEEPSEEK_GLM_MODELS: () => OPENCODE_GO_DEEPSEEK_GLM_MODELS,
19455
20319
  OPENCODE_GO_MODEL_PATTERN: () => OPENCODE_GO_MODEL_PATTERN,
19456
20320
  OPENCODE_GO_PREFIX: () => OPENCODE_GO_PREFIX,
19457
20321
  OPENROUTER_MAX_CALL_COST_USD: () => OPENROUTER_MAX_CALL_COST_USD,
@@ -19471,10 +20335,12 @@ __export(dist_exports, {
19471
20335
  PLAN_FEATURES: () => PLAN_FEATURES,
19472
20336
  PLAN_FEATURE_LOCKED_CODE: () => PLAN_FEATURE_LOCKED_CODE,
19473
20337
  PLAN_ORDER: () => PLAN_ORDER,
20338
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR: () => PLATFORM_ADMIN_CANCELLED_RUN_ERROR,
19474
20339
  PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
19475
20340
  PLATFORM_DEFAULT_PROVIDER: () => PLATFORM_DEFAULT_PROVIDER,
19476
20341
  PLATFORM_PLAN_BILLING: () => PLATFORM_PLAN_BILLING,
19477
20342
  PLATFORM_SERVER_APPLICATION_NAME: () => PLATFORM_SERVER_APPLICATION_NAME,
20343
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR: () => PLATFORM_SUPPORT_CANCELLED_RUN_ERROR,
19478
20344
  POST_SPECIAL_TRIAL_DAYS: () => POST_SPECIAL_TRIAL_DAYS,
19479
20345
  PRESET_DIRECT_FALLBACK: () => PRESET_DIRECT_FALLBACK,
19480
20346
  PRODUCTION_READ_ONLY_GUARD_TEMPLATE: () => PRODUCTION_READ_ONLY_GUARD_TEMPLATE,
@@ -19486,8 +20352,12 @@ __export(dist_exports, {
19486
20352
  PROJECT_OVERRIDE_FIELDS: () => PROJECT_OVERRIDE_FIELDS,
19487
20353
  PROMOTION_GATE_MAX_CODE_CHARS: () => PROMOTION_GATE_MAX_CODE_CHARS,
19488
20354
  PROMOTION_QUALITY_CHECKS: () => PROMOTION_QUALITY_CHECKS,
20355
+ PROVIDER_ACCOUNT_ID_PATTERN: () => PROVIDER_ACCOUNT_ID_PATTERN,
20356
+ PROVIDER_ACCOUNT_MODEL_PATTERN: () => PROVIDER_ACCOUNT_MODEL_PATTERN,
19489
20357
  PROVIDER_CHAINS: () => PROVIDER_CHAINS,
20358
+ PROVIDER_CHAINS_SETTING_MAX_CHARS: () => PROVIDER_CHAINS_SETTING_MAX_CHARS,
19490
20359
  PROVIDER_CHAIN_LEADS: () => PROVIDER_CHAIN_LEADS,
20360
+ PROVIDER_CHAIN_MAX_ENTRIES: () => PROVIDER_CHAIN_MAX_ENTRIES,
19491
20361
  PROVIDER_HOST_NOT_OFFERED: () => PROVIDER_HOST_NOT_OFFERED,
19492
20362
  PUBLIC_PLANS: () => PUBLIC_PLANS,
19493
20363
  QUARANTINE_CLOSE_TAGS: () => QUARANTINE_CLOSE_TAGS,
@@ -19495,10 +20365,12 @@ __export(dist_exports, {
19495
20365
  READ_ONLY_TAGS: () => READ_ONLY_TAGS,
19496
20366
  RECHECKED_NOTES: () => RECHECKED_NOTES,
19497
20367
  RECORDING_VALUE_PLACEHOLDERS: () => RECORDING_VALUE_PLACEHOLDERS,
20368
+ RENDER_IDENTITY_MAX_LENGTH: () => RENDER_IDENTITY_MAX_LENGTH,
19498
20369
  REPOSITORY_CLIS: () => REPOSITORY_CLIS,
19499
20370
  RESERVED_PROJECT_KEYS: () => RESERVED_PROJECT_KEYS,
19500
20371
  RESULT_STRUCTURE_TOO_COSTLY_CODE: () => RESULT_STRUCTURE_TOO_COSTLY_CODE,
19501
20372
  RETRIED_ATTEMPTS_KEY: () => RETRIED_ATTEMPTS_KEY,
20373
+ RETRY_HELD_RUN_ERROR: () => RETRY_HELD_RUN_ERROR,
19502
20374
  RUNNER_INTERRUPT_ERROR_PREFIX: () => RUNNER_INTERRUPT_ERROR_PREFIX,
19503
20375
  RUN_INTERRUPTED_ERROR_PREFIXES: () => RUN_INTERRUPTED_ERROR_PREFIXES,
19504
20376
  RUN_INTERRUPTION_NOT_RETRIED_MARKERS: () => RUN_INTERRUPTION_NOT_RETRIED_MARKERS,
@@ -19511,6 +20383,8 @@ __export(dist_exports, {
19511
20383
  SHARED_SERVER_APPLICATION_NAME: () => SHARED_SERVER_APPLICATION_NAME,
19512
20384
  SPEND_SOURCE_KEY: () => SPEND_SOURCE_KEY,
19513
20385
  SPEND_SOURCE_SERVER: () => SPEND_SOURCE_SERVER,
20386
+ STATED_LIMITS_VERSION: () => STATED_LIMITS_VERSION,
20387
+ STATED_PATTERN_MAX: () => STATED_PATTERN_MAX,
19514
20388
  STATIC_STANDARD_MIN_INTERACTIVE: () => STATIC_STANDARD_MIN_INTERACTIVE,
19515
20389
  STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
19516
20390
  SUCCESS_NOTICE_TEXT: () => SUCCESS_NOTICE_TEXT,
@@ -19540,14 +20414,20 @@ __export(dist_exports, {
19540
20414
  UI_VARIANTS: () => UI_VARIANTS,
19541
20415
  UNEXPECTED_PASS_ERROR_PREFIX: () => UNEXPECTED_PASS_ERROR_PREFIX,
19542
20416
  USER_CANCELLED_RUN_ERROR: () => USER_CANCELLED_RUN_ERROR,
20417
+ VISUAL_MASK_SELECTORS_MAX: () => VISUAL_MASK_SELECTORS_MAX,
20418
+ VISUAL_MASK_SELECTOR_FORBIDDEN: () => VISUAL_MASK_SELECTOR_FORBIDDEN,
20419
+ VISUAL_MASK_SELECTOR_MAX_LENGTH: () => VISUAL_MASK_SELECTOR_MAX_LENGTH,
20420
+ VISUAL_V1_IDENTITY: () => VISUAL_V1_IDENTITY,
19543
20421
  VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
19544
20422
  WEB_PROXY_CHAIN_HEADER: () => WEB_PROXY_CHAIN_HEADER,
19545
20423
  WITHHELD_CREDENTIAL_TEXT: () => WITHHELD_CREDENTIAL_TEXT,
19546
20424
  ZAI_MODEL_PATTERN: () => ZAI_MODEL_PATTERN,
19547
20425
  ZAI_PREFIX: () => ZAI_PREFIX,
20426
+ accountServesModel: () => accountServesModel,
19548
20427
  accumulateStatements: () => accumulateStatements,
19549
20428
  adoptBrokerFailover: () => adoptBrokerFailover,
19550
20429
  aiCostToCreditCents: () => aiCostToCreditCents,
20430
+ allowedProviderChainHosts: () => allowedProviderChainHosts,
19551
20431
  alternatesAtTop: () => alternatesAtTop,
19552
20432
  analyzeDerivedValues: () => analyzeDerivedValues,
19553
20433
  analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
@@ -19560,6 +20440,7 @@ __export(dist_exports, {
19560
20440
  analyzeUnreachedUrlWaits: () => analyzeUnreachedUrlWaits,
19561
20441
  analyzeUrlWaits: () => analyzeUrlWaits,
19562
20442
  analyzeWriteResponse: () => analyzeWriteResponse,
20443
+ androidResourceOwners: () => androidResourceOwners,
19563
20444
  apiModelCardsFromConfig: () => apiModelCardsFromConfig,
19564
20445
  apiModelsForProvider: () => apiModelsForProvider,
19565
20446
  apiProviderModelLabel: () => apiProviderModelLabel,
@@ -19570,6 +20451,7 @@ __export(dist_exports, {
19570
20451
  applyLaunchSpecialPlan: () => applyLaunchSpecialPlan,
19571
20452
  asPlainExpect: () => asPlainExpect,
19572
20453
  assertionOnlyCode: () => assertionOnlyCode,
20454
+ assertionStrengthHoistedReadsOn: () => assertionStrengthHoistedReadsOn,
19573
20455
  assertionsReadVisibleContent: () => assertionsReadVisibleContent,
19574
20456
  assessCodeOutcomeCoverage: () => assessCodeOutcomeCoverage,
19575
20457
  assessTitleCoverage: () => assessTitleCoverage,
@@ -19653,6 +20535,7 @@ __export(dist_exports, {
19653
20535
  deepgramSpeak: () => deepgramSpeak,
19654
20536
  deepgramTranscribe: () => deepgramTranscribe,
19655
20537
  defaultConfirmModel: () => defaultConfirmModel,
20538
+ defaultProviderChainEntries: () => defaultProviderChainEntries,
19656
20539
  defaultScreenModel: () => defaultScreenModel,
19657
20540
  defaultVoiceForTtsModel: () => defaultVoiceForTtsModel,
19658
20541
  deriveAuthPlan: () => deriveAuthPlan,
@@ -19695,6 +20578,7 @@ __export(dist_exports, {
19695
20578
  extractOutcomeSearchTerms: () => extractOutcomeSearchTerms,
19696
20579
  featureForAiCallType: () => featureForAiCallType,
19697
20580
  featureForMcpTool: () => featureForMcpTool,
20581
+ featureRowRunStatus: () => featureRowRunStatus,
19698
20582
  findBracedClause: () => findBracedClause,
19699
20583
  findCounterIdPins: () => findCounterIdPins,
19700
20584
  findCredentialTextLeftover: () => findCredentialTextLeftover,
@@ -19743,6 +20627,7 @@ __export(dist_exports, {
19743
20627
  getPlanChangeType: () => getPlanChangeType,
19744
20628
  getPlanDefinition: () => getPlanDefinition,
19745
20629
  getPlanLabel: () => getPlanLabel,
20630
+ getProviderChainOverrides: () => getProviderChainOverrides,
19746
20631
  getTrialDaysRemaining: () => getTrialDaysRemaining,
19747
20632
  googleBrokerNavigationUrl: () => googleBrokerNavigationUrl,
19748
20633
  googleBrokerUnavailableLine: () => googleBrokerUnavailableLine,
@@ -19756,10 +20641,10 @@ __export(dist_exports, {
19756
20641
  hasEncodedRequestBody: () => hasEncodedRequestBody,
19757
20642
  hasEngineSpecParts: () => hasEngineSpecParts,
19758
20643
  hasOnlyConstantAssertions: () => hasOnlyConstantAssertions,
19759
- hasOpenCodeGo2Prefix: () => hasOpenCodeGo2Prefix,
19760
20644
  hasOpenCodeGoPrefix: () => hasOpenCodeGoPrefix,
19761
20645
  hasOpenRouterPrefix: () => hasOpenRouterPrefix,
19762
20646
  hasPlatformLocator: () => hasPlatformLocator,
20647
+ hasProviderChainLeadsByDefault: () => hasProviderChainLeadsByDefault,
19763
20648
  hasReadOnlyMarker: () => hasReadOnlyMarker,
19764
20649
  hasSurveyNotes: () => hasSurveyNotes,
19765
20650
  hasTestFailContract: () => hasTestFailContract,
@@ -19785,8 +20670,11 @@ __export(dist_exports, {
19785
20670
  isAuthTokenName: () => isAuthTokenName,
19786
20671
  isBlockedEnvVar: () => isBlockedEnvVar,
19787
20672
  isBrowserCliEngine: () => isBrowserCliEngine,
20673
+ isBrowserVersion: () => isBrowserVersion,
19788
20674
  isBugTestBrokenError: () => isBugTestBrokenError,
20675
+ isCancelledReason: () => isCancelledReason,
19789
20676
  isCanvasUnsupportedMessage: () => isCanvasUnsupportedMessage,
20677
+ isChainablePrimary: () => isChainablePrimary,
19790
20678
  isCliEngineModelId: () => isCliEngineModelId,
19791
20679
  isCliEngineSpec: () => isCliEngineSpec,
19792
20680
  isCodeFactNote: () => isCodeFactNote,
@@ -19810,6 +20698,7 @@ __export(dist_exports, {
19810
20698
  isIdentityMobileXpath: () => isIdentityMobileXpath,
19811
20699
  isIdentityProviderSurface: () => isIdentityProviderSurface,
19812
20700
  isIncidentalAssetPath: () => isIncidentalAssetPath,
20701
+ isIntentionalNegativeAuthTest: () => isIntentionalNegativeAuthTest,
19813
20702
  isIpOrLocalhost: () => isIpOrLocalhost,
19814
20703
  isKnownPageType: () => isKnownPageType,
19815
20704
  isLaunchSpecialActive: () => isLaunchSpecialActive,
@@ -19837,6 +20726,7 @@ __export(dist_exports, {
19837
20726
  isRunInterruptedError: () => isRunInterruptedError,
19838
20727
  isRunnableStep: () => isRunnableStep,
19839
20728
  isSafeDeepLink: () => isSafeDeepLink,
20729
+ isSafeMaskSelector: () => isSafeMaskSelector,
19840
20730
  isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
19841
20731
  isSelectableApiModel: () => isSelectableApiModel,
19842
20732
  isSelfServePlan: () => isSelfServePlan,
@@ -19900,6 +20790,7 @@ __export(dist_exports, {
19900
20790
  normalizeFinishReason: () => normalizeFinishReason,
19901
20791
  normalizeNoteText: () => normalizeNoteText,
19902
20792
  normalizePageType: () => normalizePageType,
20793
+ normalizeProviderAccountModels: () => normalizeProviderAccountModels,
19903
20794
  normalizeTtsModel: () => normalizeTtsModel,
19904
20795
  normalizeTtsVoiceId: () => normalizeTtsVoiceId,
19905
20796
  noteChatFailover: () => noteChatFailover,
@@ -19924,13 +20815,18 @@ __export(dist_exports, {
19924
20815
  parseGeminiModelsPayload: () => parseGeminiModelsPayload,
19925
20816
  parseGoogleAuthorizeUrl: () => parseGoogleAuthorizeUrl,
19926
20817
  parseGoogleGisRequestUrl: () => parseGoogleGisRequestUrl,
20818
+ parseLastActionProofMode: () => parseLastActionProofMode,
19927
20819
  parseLimitParam: () => parseLimitParam,
19928
20820
  parseOffsetParam: () => parseOffsetParam,
19929
20821
  parsePageParam: () => parsePageParam,
19930
20822
  parsePgIntSetting: () => parsePgIntSetting,
19931
20823
  parseProjectEngineOverrides: () => parseProjectEngineOverrides,
19932
20824
  parsePromotionGateMode: () => parsePromotionGateMode,
20825
+ parseProviderAccountLists: () => parseProviderAccountLists,
20826
+ parseProviderChainOverrides: () => parseProviderChainOverrides,
19933
20827
  parseRecheckClearMode: () => parseRecheckClearMode,
20828
+ parseRenderIdentity: () => parseRenderIdentity,
20829
+ parseStoredProviderChainOverrides: () => parseStoredProviderChainOverrides,
19934
20830
  parseSurveyEntryUrls: () => parseSurveyEntryUrls,
19935
20831
  parseSuspiciousPassMode: () => parseSuspiciousPassMode,
19936
20832
  parseTaskModelUpdate: () => parseTaskModelUpdate,
@@ -19961,7 +20857,9 @@ __export(dist_exports, {
19961
20857
  projectAppHosts: () => projectAppHosts,
19962
20858
  providerChainFor: () => providerChainFor,
19963
20859
  providerChainHostOf: () => providerChainHostOf,
20860
+ providerChainHostProvider: () => providerChainHostProvider,
19964
20861
  providerChainLeadsFor: () => providerChainLeadsFor,
20862
+ providerChainModelKey: () => providerChainModelKey,
19965
20863
  providerChainPrimariesForHop: () => providerChainPrimariesForHop,
19966
20864
  providerHostPlan: () => providerHostPlan,
19967
20865
  readBalanced: () => readBalanced,
@@ -19971,6 +20869,7 @@ __export(dist_exports, {
19971
20869
  readEnabledEngines: () => readEnabledEngines,
19972
20870
  readProjectEngineOverrides: () => readProjectEngineOverrides,
19973
20871
  readRetriedAttempts: () => readRetriedAttempts,
20872
+ readStatedLimits: () => readStatedLimits,
19974
20873
  readStaticPageCanvasSignals: () => readStaticPageCanvasSignals,
19975
20874
  readSurveyEntryUrls: () => readSurveyEntryUrls,
19976
20875
  readSurveyNotes: () => readSurveyNotes,
@@ -19983,6 +20882,7 @@ __export(dist_exports, {
19983
20882
  regexLiteralEnd: () => regexLiteralEnd2,
19984
20883
  regexOpensAfter: () => regexOpensAfter,
19985
20884
  registrableDomain: () => registrableDomain,
20885
+ renderIdentity: () => renderIdentity,
19986
20886
  renderSurveyNotesForPrompt: () => renderSurveyNotesForPrompt,
19987
20887
  repairPageType: () => repairPageType,
19988
20888
  replaceBlockComments: () => replaceBlockComments,
@@ -20030,11 +20930,14 @@ __export(dist_exports, {
20030
20930
  selectorCandidatesForStep: () => selectorCandidatesForStep,
20031
20931
  selectorForValue: () => selectorForValue,
20032
20932
  setAgenticCliRuntime: () => setAgenticCliRuntime,
20933
+ setProviderChainOverrides: () => setProviderChainOverrides,
20033
20934
  signupPlanFor: () => signupPlanFor,
20034
20935
  simplifyMobileWanderingInPlace: () => simplifyMobileWanderingInPlace,
20035
20936
  siteOf: () => siteOf,
20036
20937
  specItemsFromTestPlans: () => specItemsFromTestPlans,
20037
20938
  splitAnswerLines: () => splitAnswerLines,
20939
+ statedLimitsFromAttrs: () => statedLimitsFromAttrs,
20940
+ statedLimitsRecord: () => statedLimitsRecord,
20038
20941
  stepAppliesToPlatform: () => stepAppliesToPlatform,
20039
20942
  stepConsoleLines: () => stepConsoleLines,
20040
20943
  stepIsDivergentForPlatform: () => stepIsDivergentForPlatform,
@@ -20046,7 +20949,10 @@ __export(dist_exports, {
20046
20949
  stripCodeFactPrefix: () => stripCodeFactPrefix,
20047
20950
  stripCoverageComments: () => stripCoverageComments,
20048
20951
  stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
20952
+ stripFractionZeros: () => stripFractionZeros,
20953
+ stripLineComment: () => stripLineComment,
20049
20954
  stripLoginScaffolding: () => stripLoginScaffolding,
20955
+ stripTrailingChars: () => stripTrailingChars,
20050
20956
  stripTrailingSlashes: () => stripTrailingSlashes,
20051
20957
  strongerNeedsReviewSource: () => strongerNeedsReviewSource,
20052
20958
  sttModelsForProvider: () => sttModelsForProvider,
@@ -20064,10 +20970,14 @@ __export(dist_exports, {
20064
20970
  ttsFallbackModel: () => ttsFallbackModel,
20065
20971
  ttsProviderForModel: () => ttsProviderForModel,
20066
20972
  unexpectedPassError: () => unexpectedPassError,
20973
+ v1RenderIdentity: () => v1RenderIdentity,
20067
20974
  validateAppMapDocument: () => validateAppMapDocument,
20068
20975
  validateAssertionStrength: () => validateAssertionStrength,
20069
20976
  validateEngineSpec: () => validateEngineSpec,
20977
+ validateProviderChainEntries: () => validateProviderChainEntries,
20978
+ validateProviderChainsSetting: () => validateProviderChainsSetting,
20070
20979
  validateTaskModelId: () => validateTaskModelId,
20980
+ visualBaselinesEnabled: () => visualBaselinesEnabled,
20071
20981
  visualProDailyBudget: () => visualProDailyBudget,
20072
20982
  voiceMatchesTtsModel: () => voiceMatchesTtsModel,
20073
20983
  voicesForTtsModel: () => voicesForTtsModel,
@@ -20130,11 +21040,14 @@ var init_dist = __esm({
20130
21040
  init_deepgram_voices();
20131
21041
  init_feature_flags();
20132
21042
  init_env_flags();
21043
+ init_visual_compare_limits();
21044
+ init_visual_baseline_identity();
20133
21045
  init_analytics_url();
20134
21046
  init_agentic_cli_runtime();
20135
21047
  init_api_model_catalog();
20136
21048
  init_engine_spec();
20137
21049
  init_provider_chains();
21050
+ init_provider_accounts();
20138
21051
  init_project_engine_overrides();
20139
21052
  init_ai_profiles();
20140
21053
  init_openrouter_usage();
@@ -20176,6 +21089,7 @@ var init_dist = __esm({
20176
21089
  init_regex_alternation();
20177
21090
  init_page_evidence();
20178
21091
  init_trailing_slashes();
21092
+ init_stated_limits();
20179
21093
  }
20180
21094
  });
20181
21095
 
@@ -20263,7 +21177,7 @@ var require_runs_dir = __commonJS({
20263
21177
  } catch (error2) {
20264
21178
  if (error2.code === "EEXIST" && resolvesFrom(runsDir) === own)
20265
21179
  return;
20266
- throw new Error(`Could not link ${link} to the runner's node_modules (${error2.message}). Set RUNS_DIR to a writable folder outside node_modules that can resolve @playwright/test.`);
21180
+ throw new Error(`Could not link ${link} to the runner's node_modules (${error2.message}). Set RUNS_DIR to a writable folder outside node_modules that can resolve @playwright/test.`, { cause: error2 });
20267
21181
  }
20268
21182
  }
20269
21183
  }
@@ -21866,23 +22780,79 @@ var require_harness_init_script = __commonJS({
21866
22780
  exports2.isHarnessInitConsoleLine = isHarnessInitConsoleLine;
21867
22781
  exports2.stripHarnessConsoleEntries = stripHarnessConsoleEntries;
21868
22782
  var node_crypto_1 = require("crypto");
22783
+ var shared_1 = (init_dist(), __toCommonJS(dist_exports));
21869
22784
  exports2.HARNESS_INIT_SOURCE_URL = `vqa-harness-init-${(0, node_crypto_1.randomBytes)(4).toString("hex")}.js`;
22785
+ var TRANSITION_LOOP_LIMIT = 8;
22786
+ var TRANSITION_LOOP_WINDOW_MS = 1e3;
21870
22787
  function withHarnessSourceUrl(content) {
21871
22788
  return `${content}
21872
22789
  //# sourceURL=${exports2.HARNESS_INIT_SOURCE_URL}`;
21873
22790
  }
21874
22791
  function buildAnimationDisablingSource(opts) {
22792
+ const finishTransitions = opts.finishTransitions ?? (0, shared_1.parseBoolFlag)(process.env.HARNESS_FINISH_CSS_TRANSITIONS);
22793
+ const css = finishTransitions ? "*, *::before, *::after { animation-duration: 0s !important; animation-delay: 0s !important; }" : "*, *::before, *::after { transition-duration: 0s !important; animation-duration: 0s !important; transition-delay: 0s !important; animation-delay: 0s !important; }";
21875
22794
  return withHarnessSourceUrl(`(() => {
22795
+ // An error the browser throws for the page's own value, passed through one of the wrappers below (a bad
22796
+ // adoptedStyleSheets list, animate timing it refuses), is the app's: stock Playwright reports it as a page error.
22797
+ // Thrown from our wrapper, its top frame would be this script, and the console evidence drops such an error as
22798
+ // the runner's own. Our frames come out of its stack, so the app's call is the top frame, as without the harness.
22799
+ // (Under Error.stackTraceLimit the stack then has one or two frames fewer than stock's; the top frame is right.)
22800
+ const own = ${JSON.stringify(exports2.HARNESS_INIT_SOURCE_URL)};
22801
+ const appError = (e) => {
22802
+ try {
22803
+ const stack = e !== null && typeof e === 'object' ? e.stack : undefined;
22804
+ if (typeof stack === 'string' && stack.includes(own)) e.stack = stack.split('\\n').filter((line) => !line.includes(own)).join('\\n');
22805
+ } catch (x) {}
22806
+ return e;
22807
+ };
21876
22808
  // Layer 1: CSS transitions and animations - instant, not removed
21877
22809
  // (duration 0s preserves final state; 'none' would prevent keyframes from completing)
22810
+ // The rule goes in a constructed stylesheet on document.adoptedStyleSheets, which adds no node to the DOM:
22811
+ // a <style> element changed what the test sees (locator('style') and 'head > *' counts, document.styleSheets,
22812
+ // an iframe's empty head), so a spec stock Playwright passes failed. The <style> is the fallback for a
22813
+ // browser with no constructable stylesheets.
22814
+ let adopted = false;
21878
22815
  try {
22816
+ if (typeof CSSStyleSheet === 'function' && typeof Document === 'function' && 'adoptedStyleSheets' in Document.prototype) {
22817
+ const sheet = new CSSStyleSheet();
22818
+ sheet.replaceSync(${JSON.stringify(css)});
22819
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
22820
+ adopted = true;
22821
+ // A page that assigns the list without spreading it (document.adoptedStyleSheets = [mine]) would drop the
22822
+ // harness sheet and bring animations back: keep it, last, on every assignment. A bad value (not iterable,
22823
+ // or a list the browser refuses) goes to the browser, which throws its own error as the app's (appError).
22824
+ try {
22825
+ const desc = Object.getOwnPropertyDescriptor(Document.prototype, 'adoptedStyleSheets');
22826
+ if (desc && desc.get && desc.set) {
22827
+ Object.defineProperty(document, 'adoptedStyleSheets', {
22828
+ configurable: true,
22829
+ enumerable: desc.enumerable,
22830
+ get() { return desc.get.call(document); },
22831
+ set(value) {
22832
+ try {
22833
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function') || typeof value[Symbol.iterator] !== 'function') {
22834
+ desc.set.call(document, value);
22835
+ return;
22836
+ }
22837
+ const list = [...value];
22838
+ desc.set.call(document, list.includes(sheet) ? list : [...list, sheet]);
22839
+ } catch (e) {
22840
+ throw appError(e);
22841
+ }
22842
+ },
22843
+ });
22844
+ }
22845
+ } catch (e) {}
22846
+ }
22847
+ } catch (e) {}
22848
+ if (!adopted) try {
21879
22849
  let applied = false;
21880
22850
  const apply = () => {
21881
22851
  if (applied) return true;
21882
22852
  const root = document.head || document.documentElement;
21883
22853
  if (!root) return false;
21884
22854
  const style = document.createElement('style');
21885
- style.textContent = '*, *::before, *::after { transition-duration: 0s !important; animation-duration: 0s !important; transition-delay: 0s !important; animation-delay: 0s !important; }';
22855
+ style.textContent = ${JSON.stringify(css)};
21886
22856
  root.appendChild(style);
21887
22857
  applied = true;
21888
22858
  return true;
@@ -21904,19 +22874,96 @@ ${opts.hideWebdriver ? `
21904
22874
  try {
21905
22875
  Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
21906
22876
  } catch (e) {}
22877
+ ` : ""}
22878
+ ${finishTransitions ? `
22879
+ // Layer 1b: CSS transitions run, and are finished as they start, so transitionend fires.
22880
+ // - finish() is deferred to a task: called inside the transitionrun dispatch (or a microtask of it) after a
22881
+ // real input event, Chromium finishes the transition but never fires its transitionstart/transitionend.
22882
+ // - setTimeout and performance.now are the ones present when this script runs, before the page's own
22883
+ // scripts: fake timers the page installs later cannot hold a finish back.
22884
+ // - The subtree is walked only for a pseudo-element's transition (its animations hang off the element).
22885
+ // - An element whose same transition restarts more than ${TRANSITION_LOOP_LIMIT} times within ${TRANSITION_LOOP_WINDOW_MS} ms (an app that starts it again on
22886
+ // each transitionend: carousel, pulse) is left to run at its own pace from then on, instead of cycling at
22887
+ // frame rate for the rest of the run.
22888
+ try {
22889
+ const st = setTimeout;
22890
+ const perf = typeof performance === 'object' && performance && typeof performance.now === 'function' ? performance : null;
22891
+ const now = perf ? perf.now.bind(perf) : Date.now;
22892
+ const seen = new WeakMap();
22893
+ const looping = (el, key) => {
22894
+ let byKey = seen.get(el);
22895
+ if (!byKey) { byKey = new Map(); seen.set(el, byKey); }
22896
+ const t = now();
22897
+ let s = byKey.get(key);
22898
+ if (s && s.loop) return true;
22899
+ if (!s || t - s.since > ${TRANSITION_LOOP_WINDOW_MS}) { s = { n: 0, since: t, loop: false }; byKey.set(key, s); }
22900
+ s.n += 1;
22901
+ if (s.n > ${TRANSITION_LOOP_LIMIT}) s.loop = true;
22902
+ return s.loop;
22903
+ };
22904
+ window.addEventListener('transitionrun', (ev) => {
22905
+ try {
22906
+ const el = ev && ev.target;
22907
+ if (!el || typeof el.getAnimations !== 'function' || typeof CSSTransition !== 'function') return;
22908
+ const pseudo = ev.pseudoElement || '';
22909
+ if (looping(el, ev.propertyName + '|' + pseudo)) return;
22910
+ for (const a of (pseudo ? el.getAnimations({ subtree: true }) : el.getAnimations())) {
22911
+ if (a instanceof CSSTransition && a.transitionProperty === ev.propertyName && a.effect
22912
+ && a.effect.target === el && (a.effect.pseudoElement || '') === pseudo) {
22913
+ st(() => { try { a.finish(); } catch (e) {} }, 0);
22914
+ }
22915
+ }
22916
+ } catch (e) {}
22917
+ }, true);
22918
+ } catch (e) {}
21907
22919
  ` : ""}
21908
22920
  // Layer 2: Web Animations API - make instant instead of no-op
21909
22921
  // (preserves animation completion callbacks that app code may depend on)
22922
+ // Timing the browser refuses (a negative, NaN or non-'auto' duration, a delay that is not finite) goes to the
22923
+ // browser as the page wrote it: zeroed, it raised no TypeError, so a page error stock Playwright reports was hidden.
22924
+ // Options that are not a plain object (an array, a class instance) keep what they inherit: the browser reads
22925
+ // every timing member through the prototype chain, and an array's inherited fill() is a TypeError. A copy kept
22926
+ // only own properties, so the error was hidden; a proxy overrides only duration and delay.
21910
22927
  try {
21911
22928
  const origAnimate = Element.prototype.animate;
22929
+ const refused = (options) => {
22930
+ try {
22931
+ if (options === undefined || options === null) return false;
22932
+ if (typeof options !== 'object' && typeof options !== 'function') return !(Number(options) >= 0);
22933
+ const duration = options.duration;
22934
+ if (duration !== undefined && duration !== 'auto' && !(typeof duration === 'number' && duration >= 0)) return true;
22935
+ const delay = options.delay;
22936
+ return delay !== undefined && !Number.isFinite(Number(delay));
22937
+ } catch (e) {
22938
+ return true;
22939
+ }
22940
+ };
22941
+ const instant = (options) => {
22942
+ try {
22943
+ // Plain: its prototype is null or an Object.prototype (of any frame's realm), so it inherits no timing.
22944
+ const proto = Object.getPrototypeOf(options);
22945
+ if (proto !== null && Object.getPrototypeOf(proto) !== null && typeof Proxy === 'function') {
22946
+ // The proxy's target is an empty object, not the options: a target with a read-only, non-configurable
22947
+ // duration or delay (a frozen instance, Object.create with a descriptor) would make returning 0 a proxy
22948
+ // invariant TypeError. Every other member is read from the options, the options as the receiver.
22949
+ return new Proxy({}, { get: (_target, key) => (key === 'duration' || key === 'delay' ? 0 : Reflect.get(options, key)) });
22950
+ }
22951
+ } catch (e) {}
22952
+ return Object.assign({}, options, { duration: 0, delay: 0 });
22953
+ };
21912
22954
  if (typeof origAnimate === 'function') {
21913
22955
  Element.prototype.animate = function (keyframes, options) {
21914
- if (typeof options === 'object' && options !== null) {
21915
- options = Object.assign({}, options, { duration: 0, delay: 0 });
21916
- } else {
21917
- options = 0; // covers number, undefined, and any other type
22956
+ try {
22957
+ if (refused(options)) return origAnimate.call(this, keyframes, options);
22958
+ if (typeof options === 'object' && options !== null) {
22959
+ options = instant(options);
22960
+ } else {
22961
+ options = 0; // covers number, undefined, and any other type
22962
+ }
22963
+ return origAnimate.call(this, keyframes, options);
22964
+ } catch (e) {
22965
+ throw appError(e);
21918
22966
  }
21919
- return origAnimate.call(this, keyframes, options);
21920
22967
  };
21921
22968
  }
21922
22969
  } catch (e) {}
@@ -22795,6 +23842,7 @@ var require_browser_config = __commonJS({
22795
23842
  exports2.fullChromiumCloudLaunch = fullChromiumCloudLaunch;
22796
23843
  exports2.chooseCloudBrowser = chooseCloudBrowser;
22797
23844
  exports2.appTimezoneConfigLine = appTimezoneConfigLine;
23845
+ exports2.discoveryTimezoneId = discoveryTimezoneId;
22798
23846
  exports2.getMCPBrowserConfig = getMCPBrowserConfig;
22799
23847
  exports2.getPlaywrightLaunchOptions = getPlaywrightLaunchOptions;
22800
23848
  exports2.getLaunchOptions = getLaunchOptions;
@@ -23013,7 +24061,7 @@ var require_browser_config = __commonJS({
23013
24061
  try {
23014
24062
  (0, fs_1.cpSync)(localStateSrc, (0, path_1.join)(tempDir, "Local State"));
23015
24063
  } catch (err) {
23016
- throw new Error(`Cannot copy Chrome profile (${err instanceof Error ? err.message : String(err)}). Quit Google Chrome completely, or use getChromeUserDataDirSource() for seeding.`);
24064
+ throw new Error(`Cannot copy Chrome profile (${err instanceof Error ? err.message : String(err)}). Quit Google Chrome completely, or use getChromeUserDataDirSource() for seeding.`, { cause: err });
23017
24065
  }
23018
24066
  }
23019
24067
  for (const file of essentialFiles) {
@@ -23204,6 +24252,10 @@ var require_browser_config = __commonJS({
23204
24252
  const zone = (0, shared_1.appTimezoneFromVariables)(vars);
23205
24253
  return zone ? `timezoneId: ${JSON.stringify(zone)},` : "";
23206
24254
  }
24255
+ function discoveryTimezoneId(ctx) {
24256
+ const vars = ctx.variableContext;
24257
+ return (0, shared_1.appTimezoneFromVariables)(vars?.publicTestVariables) ?? (0, shared_1.appTimezoneFromVariables)(vars?.allTestVariables);
24258
+ }
23207
24259
  function getMCPBrowserConfig(opts) {
23208
24260
  const userDataDir = opts?.forceIsolated ? null : getTestUserDataDir();
23209
24261
  const timezoneOption = opts?.timezoneId ? { timezoneId: opts.timezoneId } : {};
@@ -23990,7 +25042,7 @@ var require_survey_door_harvester = __commonJS({
23990
25042
  }
23991
25043
  if (!payload)
23992
25044
  continue;
23993
- const cleaned = payload.replace(/^\s*```(json)?\s*\n?/, "").replace(/\n?```\s*$/, "").trim();
25045
+ const cleaned = payload.replace(/^\s*```(json)?\s*\n?/i, "").replace(/\n?```\s*$/, "").trim();
23994
25046
  if (!cleaned)
23995
25047
  continue;
23996
25048
  try {