@validate.qa/runner 1.0.100 → 1.0.102

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-MBKGTELT.mjs → chunk-3JM6XTSZ.mjs} +1862 -530
  3. package/dist/{chunk-2XD63NU5.mjs → chunk-4WLP3RWN.mjs} +941 -86
  4. package/dist/{chunk-MHFP3QXU.mjs → chunk-5DFBEL72.mjs} +1 -1
  5. package/dist/{chunk-II7QW7MZ.mjs → chunk-BXLEPT4M.mjs} +31 -18
  6. package/dist/{chunk-MAO6ADAO.mjs → chunk-CBVHPYVJ.mjs} +62 -2
  7. package/dist/{chunk-5P4ANVDY.mjs → chunk-DN3O6EYT.mjs} +43073 -23357
  8. package/dist/{chunk-GIIIVO2W.mjs → chunk-E2RRAE27.mjs} +155 -11
  9. package/dist/chunk-E2VEMBQQ.mjs +291 -0
  10. package/dist/{chunk-HMUC3DKS.mjs → chunk-HJ6FKHMM.mjs} +2281 -2
  11. package/dist/{chunk-WDXLAZ2J.mjs → chunk-PWFWMVZU.mjs} +3 -2
  12. package/dist/{chunk-6NIT2CCH.mjs → chunk-ZZYBRIBT.mjs} +18 -12
  13. package/dist/cli.js +139723 -114754
  14. package/dist/cli.mjs +536 -247
  15. package/dist/credential-mcp-server.js +1761 -358
  16. package/dist/credential-mcp-server.mjs +3 -3
  17. package/dist/discovery-mcp-server.js +1874 -379
  18. package/dist/discovery-mcp-server.mjs +4 -4
  19. package/dist/dist-52M5RDZ6.mjs +17 -0
  20. package/dist/inbox-mcp-server.js +1902 -505
  21. package/dist/inbox-mcp-server.mjs +3 -3
  22. package/dist/route-seed-worker.js +3976 -356
  23. package/dist/route-seed-worker.mjs +14 -7
  24. package/dist/run-write-guard-preload.js +2908 -572
  25. package/dist/run-write-guard-preload.mjs +5 -285
  26. package/dist/run-write-guard.js +2908 -572
  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 +4 -3
  31. package/dist/dist-DFNLMH6L.mjs +0 -16
@@ -91,13 +91,13 @@ function isBlockedEnvVar(key) {
91
91
  function sanitizeTestVariables(vars) {
92
92
  if (!vars)
93
93
  return {};
94
- const clean2 = {};
94
+ const clean3 = {};
95
95
  for (const [key, value] of Object.entries(vars)) {
96
96
  if (!isBlockedEnvVar(key)) {
97
- clean2[key] = value;
97
+ clean3[key] = value;
98
98
  }
99
99
  }
100
- return clean2;
100
+ return clean3;
101
101
  }
102
102
  function buildSandboxedEnv(processEnv, testVariables) {
103
103
  const env = {};
@@ -1554,9 +1554,40 @@ function semanticTarget(label, platform, base) {
1554
1554
  }
1555
1555
  };
1556
1556
  }
1557
+ function androidResourceOwners(value) {
1558
+ const owners = [];
1559
+ for (const match of value.matchAll(ANDROID_RESOURCE_ID_SUFFIX_RE)) {
1560
+ let runStart = match.index;
1561
+ while (runStart > 0 && /[a-z0-9_.]/i.test(value[runStart - 1]))
1562
+ runStart--;
1563
+ const owner = ownerAtEndOfRun(value.slice(runStart, match.index));
1564
+ if (owner !== null)
1565
+ owners.push(owner);
1566
+ }
1567
+ return owners;
1568
+ }
1569
+ function ownerAtEndOfRun(run) {
1570
+ const segments = run.split(".");
1571
+ const last = segments.length - 1;
1572
+ const startsWithLetter = (segment) => /^[a-z]/i.test(segment);
1573
+ if (last < 1 || !startsWithLetter(segments[last]))
1574
+ return null;
1575
+ let first = last;
1576
+ while (first > 0 && startsWithLetter(segments[first - 1]))
1577
+ first--;
1578
+ let offset = 0;
1579
+ for (let i = 0; i < first - 1; i++)
1580
+ offset += segments[i].length + 1;
1581
+ if (first > 0) {
1582
+ const letterAt = segments[first - 1].search(/[a-z]/i);
1583
+ if (letterAt >= 0)
1584
+ return run.slice(offset + letterAt);
1585
+ offset += segments[first - 1].length + 1;
1586
+ }
1587
+ return first < last ? run.slice(offset) : null;
1588
+ }
1557
1589
  function containsForeignAppPackage(value, targetAppId) {
1558
- for (const match of value.matchAll(ANDROID_RESOURCE_OWNER_RE)) {
1559
- const owner = match[1];
1590
+ for (const owner of androidResourceOwners(value)) {
1560
1591
  if (targetAppId && owner !== targetAppId && !owner.startsWith(`${targetAppId}.`))
1561
1592
  return true;
1562
1593
  }
@@ -1693,7 +1724,7 @@ function healMobileTestSteps(input) {
1693
1724
  summary: changed ? uniq(summary).slice(0, 12) : []
1694
1725
  };
1695
1726
  }
1696
- var MOBILE_STEP_ACTIONS, FOREIGN_PACKAGE_RE, ANDROID_RESOURCE_OWNER_RE, COMMON_EXTERNAL_PREFIXES;
1727
+ var MOBILE_STEP_ACTIONS, FOREIGN_PACKAGE_RE, ANDROID_RESOURCE_ID_SUFFIX_RE, COMMON_EXTERNAL_PREFIXES;
1697
1728
  var init_mobile_step_healer = __esm({
1698
1729
  "../shared/dist/mobile-step-healer.js"() {
1699
1730
  "use strict";
@@ -1714,7 +1745,7 @@ var init_mobile_step_healer = __esm({
1714
1745
  "assertStateDelta"
1715
1746
  ]);
1716
1747
  FOREIGN_PACKAGE_RE = /\b[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*){2,}\b/gi;
1717
- ANDROID_RESOURCE_OWNER_RE = /([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+):id\//gi;
1748
+ ANDROID_RESOURCE_ID_SUFFIX_RE = /:id\//gi;
1718
1749
  COMMON_EXTERNAL_PREFIXES = [
1719
1750
  "com.android.",
1720
1751
  "com.google.",
@@ -3027,7 +3058,7 @@ function unescapeQuotedBody(raw) {
3027
3058
  return raw.replace(/\\(['"`\\])/g, "$1");
3028
3059
  }
3029
3060
  function extractRoleNameValue(line) {
3030
- const m = line.match(new RegExp(`getByRole\\s*\\([^,]+,\\s*\\{[^}]*name\\s*:\\s*(?:${QUOTED_BODY}|\\/([^/]*?)\\/)`, "i"));
3061
+ const m = line.match(new RegExp(`getByRole\\s*\\([^,]{1,200},\\s*\\{[^}]{0,200}?name\\s*:\\s*(?:${QUOTED_BODY}|\\/([^/]*?)\\/)`, "i"));
3031
3062
  if (!m)
3032
3063
  return null;
3033
3064
  const raw = m[1] ?? m[2] ?? m[3];
@@ -3424,7 +3455,7 @@ var init_assertion_classifier = __esm({
3424
3455
  // toHaveCount(0) stays tier 4 (absence proof) via TIER4_PATTERNS.
3425
3456
  [/\.toHaveCount\s*\(\s*[1-9]\d*\s*\)/, "non-zero toHaveCount asserts a record count that changes between runs"],
3426
3457
  // parenthesised counts like "(3)" or "[12]" in text - but not Playwright method args
3427
- [/(?<!\.\w{3,20}\s*)\(\s*\d+\s*\)(?!\s*[,;]?\s*\))/i, "parenthesised count is session-specific"],
3458
+ [/\((?<!\.\w{3,20}\s*\()\s*\d+\s*\)(?!\s*(?:[,;]\s*)?\))/i, "parenthesised count is session-specific"],
3428
3459
  // bare email addresses in assertion content
3429
3460
  [/\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b/i, "email address is user-specific"],
3430
3461
  // currency/balance amounts (dynamic account data)
@@ -3670,7 +3701,7 @@ var init_assertion_proof = __esm({
3670
3701
  PRESENCE_MATCHER = /\.(?:toBeVisible|toBeAttached|toBeEnabled|toBeInViewport|toBeEditable|toBeFocused)\s*\(|\.toHaveCount\s*\(\s*1\s*\)/;
3671
3702
  TEXT_MATCHER = /\.(?:toHaveText|toContainText)\s*\(/;
3672
3703
  ANY_VALUE_SOURCE = /^(?:\.|\\S|\\w|\\d|\[\^\\s\]|\[0-9\]|\[\\d\]|\[\\w\]|\[\\s\\S\])[+*]?\??$/;
3673
- ANY_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(\s*\/((?:\\.|[^/\\]){1,300})\/[a-z]*\s*\)/;
3704
+ ANY_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(\s*\/((?:\\.|[^/\\]){1,300})\/[a-z]*\s*[,)]/;
3674
3705
  EMPTY_NOT_VALUE = /\.not\s*\.\s*toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/;
3675
3706
  TEXT_MATCHER_LITERAL = /\.(?:toHaveText|toContainText)\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/;
3676
3707
  GET_BY_TEXT_LITERAL = /getByText\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/;
@@ -3687,7 +3718,7 @@ var init_assertion_proof = __esm({
3687
3718
  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;
3688
3719
  TEXT_LOCATOR = /\.getByText\s*\(|\.filter\s*\(\s*\{\s*hasText\s*:/g;
3689
3720
  CSS_LOCATOR = /\.locator\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/g;
3690
- SPECIFIC_CSS = /\[[^\]]*=|#[\w-]|has-text|:text/;
3721
+ SPECIFIC_CSS = /\[[^\][=]*=|#[\w-]|has-text|:text/;
3691
3722
  }
3692
3723
  });
3693
3724
 
@@ -3735,6 +3766,8 @@ function stripCoverageComments(code) {
3735
3766
  let copyFrom = 0;
3736
3767
  let i = 0;
3737
3768
  let quote = null;
3769
+ let prev = "";
3770
+ let noRegexBefore = 0;
3738
3771
  while (i < code.length) {
3739
3772
  const ch = code[i];
3740
3773
  if (quote) {
@@ -3742,12 +3775,24 @@ function stripCoverageComments(code) {
3742
3775
  i += 2;
3743
3776
  continue;
3744
3777
  }
3745
- if (ch === quote || ch === "\n" && quote !== "`")
3778
+ if (ch === quote || ch === "\n" && quote !== "`") {
3746
3779
  quote = null;
3780
+ prev = ch === "\n" ? prev : ch;
3781
+ }
3747
3782
  i++;
3748
3783
  continue;
3749
3784
  }
3750
3785
  const next = code[i + 1];
3786
+ if (ch === "/" && next !== "*" && next !== "/" && i >= noRegexBefore && (prev === "" || REGEX_PRECEDER2.includes(prev))) {
3787
+ const end = regexLiteralEnd3(code, i);
3788
+ if (end > 0) {
3789
+ i = end;
3790
+ prev = "a";
3791
+ continue;
3792
+ }
3793
+ const eol = code.indexOf("\n", i);
3794
+ noRegexBefore = eol === -1 ? code.length : eol;
3795
+ }
3751
3796
  if (ch === "/" && next === "*") {
3752
3797
  const end = code.indexOf("*/", i + 2);
3753
3798
  const stop = end === -1 ? code.length : end + 2;
@@ -3766,11 +3811,39 @@ function stripCoverageComments(code) {
3766
3811
  }
3767
3812
  if (ch === '"' || ch === "'" || ch === "`")
3768
3813
  quote = ch;
3814
+ if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r")
3815
+ prev = ch;
3769
3816
  i++;
3770
3817
  }
3771
3818
  out.push(code.slice(copyFrom));
3772
3819
  return out.join("");
3773
3820
  }
3821
+ function regexLiteralEnd3(code, start) {
3822
+ let inClass = false;
3823
+ for (let j = start + 1; j < code.length; j++) {
3824
+ const c = code[j];
3825
+ if (c === "\n")
3826
+ return -1;
3827
+ if (c === "\\") {
3828
+ j++;
3829
+ continue;
3830
+ }
3831
+ if (inClass) {
3832
+ if (c === "]")
3833
+ inClass = false;
3834
+ continue;
3835
+ }
3836
+ if (c === "[")
3837
+ inClass = true;
3838
+ else if (c === "/") {
3839
+ let k = j + 1;
3840
+ while (k < code.length && isIdentChar(code[k]))
3841
+ k++;
3842
+ return k;
3843
+ }
3844
+ }
3845
+ return -1;
3846
+ }
3774
3847
  function isIdentChar(ch) {
3775
3848
  if (!ch)
3776
3849
  return false;
@@ -4428,7 +4501,7 @@ function assessTitleCoverage(title, code) {
4428
4501
  const repeatedHit = [...repeated].some((term) => hay.includes(term));
4429
4502
  return { covered: hit >= needed || repeatedHit, missingTerms };
4430
4503
  }
4431
- var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
4504
+ var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, REGEX_PRECEDER2, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
4432
4505
  var init_expected_outcome_coverage = __esm({
4433
4506
  "../shared/dist/expected-outcome-coverage.js"() {
4434
4507
  "use strict";
@@ -4465,6 +4538,7 @@ var init_expected_outcome_coverage = __esm({
4465
4538
  ]);
4466
4539
  MAX_ASSERTION_JOIN_CHARS = 8e3;
4467
4540
  MAX_COVERAGE_LINE_CHARS = 2e3;
4541
+ REGEX_PRECEDER2 = "(,=:[!&|?{};+-*%<>~^";
4468
4542
  BLOCK_SCAFFOLD = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
4469
4543
  cachedAssertionInput = null;
4470
4544
  cachedAssertionSource = { raw: "", normalized: "", bindings: /* @__PURE__ */ new Map(), bindingRefs: /* @__PURE__ */ new Map() };
@@ -4608,22 +4682,37 @@ function classifyAuthHandoffIntent(text) {
4608
4682
  function withoutIdpUrlAsserts(code) {
4609
4683
  return code.replace(/\.(?:toHaveURL|waitForURL)\s*\((?:[^)(]|\([^)]*\))*\)/g, (match) => /accountchooser|accounts\.google|appleid\.apple|login\.microsoftonline|\/v\d+\/signin|oauth/i.test(match) ? "" : match);
4610
4684
  }
4685
+ function stripLineComment(line) {
4686
+ let from = line.length;
4687
+ while (from > 0) {
4688
+ const c = line.charCodeAt(from - 1);
4689
+ if (c === 10 || c === 13 || c === 8232 || c === 8233)
4690
+ break;
4691
+ from--;
4692
+ }
4693
+ const at = line.indexOf("//", from);
4694
+ return at < 0 ? line : line.slice(0, at);
4695
+ }
4611
4696
  function stripComments(code) {
4612
- return replaceBlockComments(code, () => "").split("\n").map((line) => line.replace(/\/\/.*$/, "")).join("\n");
4697
+ return replaceBlockComments(code, () => "").split("\n").map(stripLineComment).join("\n");
4613
4698
  }
4614
- function balancedCall(code, start) {
4615
- const open = code.indexOf("(", start);
4616
- if (open < 0)
4617
- return null;
4618
- let depth = 0;
4619
- for (let i = open; i < code.length; i++) {
4699
+ function closingParens(code) {
4700
+ const close = new Int32Array(code.length).fill(-1);
4701
+ const open = [];
4702
+ for (let i = 0; i < code.length; i++) {
4620
4703
  const c = code[i];
4621
4704
  if (c === "(")
4622
- depth++;
4623
- else if (c === ")" && --depth === 0)
4624
- return { inner: code.slice(open + 1, i), end: i };
4705
+ open.push(i);
4706
+ else if (c === ")" && open.length > 0)
4707
+ close[open.pop()] = i;
4625
4708
  }
4626
- return null;
4709
+ return close;
4710
+ }
4711
+ function balancedCall(code, close, start) {
4712
+ const open = code.indexOf("(", start);
4713
+ if (open < 0 || close[open] < 0)
4714
+ return null;
4715
+ return { inner: code.slice(open + 1, close[open]), end: close[open] };
4627
4716
  }
4628
4717
  function resolveLocator(code, subject) {
4629
4718
  const id = subject.trim().match(/^([A-Za-z_$][\w$]*)$/);
@@ -4671,8 +4760,10 @@ function isAuthHandoffDismissCode(code) {
4671
4760
  const expects = [];
4672
4761
  const re = new RegExp(EXPECT_CALL_RE.source, "g");
4673
4762
  let match;
4763
+ let close = null;
4674
4764
  while (match = re.exec(body)) {
4675
- const found = balancedCall(body, match.index);
4765
+ close ??= closingParens(body);
4766
+ const found = balancedCall(body, close, match.index);
4676
4767
  if (found)
4677
4768
  expects.push({ inner: found.inner, tail: body.slice(found.end, found.end + 48) });
4678
4769
  }
@@ -4717,12 +4808,209 @@ var init_auth_handoff_coverage = __esm({
4717
4808
  }
4718
4809
  });
4719
4810
 
4811
+ // ../shared/dist/auth-plan.js
4812
+ function hasInlineLoginSteps(playwrightCode) {
4813
+ if (!playwrightCode)
4814
+ return false;
4815
+ return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
4816
+ }
4817
+ function hasGeneratedAuthGuard(playwrightCode) {
4818
+ if (!playwrightCode)
4819
+ return false;
4820
+ return /\/\/ >>> VALIDATE_QA_AUTH v(\d+)\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\1\b/.test(playwrightCode);
4821
+ }
4822
+ function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
4823
+ if (!sharedAuthInjectEnabled)
4824
+ return false;
4825
+ if (tags.includes("@auth"))
4826
+ return false;
4827
+ if (tags.includes("@api"))
4828
+ return true;
4829
+ if (!tags.includes("@needs-auth"))
4830
+ return false;
4831
+ if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
4832
+ return true;
4833
+ const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
4834
+ const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
4835
+ return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
4836
+ }
4837
+ function codeReadsStoredCredentials(code) {
4838
+ if (!code)
4839
+ return false;
4840
+ if (/__vqaLogin\b/.test(code))
4841
+ return true;
4842
+ for (const m of code.matchAll(/TEST_[A-Z0-9_]+/g)) {
4843
+ if (/_(?:EMAIL|PASSWORD)/.test(m[0]))
4844
+ return true;
4845
+ }
4846
+ for (const m of code.matchAll(/process\.env\s*\[([^\]]*)\]?/g)) {
4847
+ if (!/^\s*(['"])[^'"`]*\1\s*$/.test(m[1]))
4848
+ return true;
4849
+ }
4850
+ return false;
4851
+ }
4852
+ function explicitAuthTypeTags(t) {
4853
+ const found = /* @__PURE__ */ new Set();
4854
+ for (const tag of t) {
4855
+ if (AUTH_TYPE_TAGS.includes(tag))
4856
+ found.add(tag);
4857
+ else if (Object.hasOwn(AUTH_TAG_ALIASES, tag))
4858
+ found.add(AUTH_TAG_ALIASES[tag]);
4859
+ }
4860
+ return [...found];
4861
+ }
4862
+ function invalidPlan(configurationError) {
4863
+ return {
4864
+ version: 1,
4865
+ source: "invalid",
4866
+ acquisition: "unsupported",
4867
+ presentation: "none",
4868
+ needsCookieState: false,
4869
+ needsStandingHeaders: false,
4870
+ standingHeadersMode: "none",
4871
+ needsLoginCreds: false,
4872
+ requiredEnvKeys: [],
4873
+ configurationError
4874
+ };
4875
+ }
4876
+ function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
4877
+ return {
4878
+ version: 1,
4879
+ source: "explicit",
4880
+ authTypeTag: authTypeTag2,
4881
+ acquisition,
4882
+ presentation,
4883
+ needsCookieState: opts.cookie,
4884
+ needsStandingHeaders: opts.standingMode === "required",
4885
+ standingHeadersMode: opts.standingMode,
4886
+ needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
4887
+ requiredEnvKeys: opts.requiredEnvKeys
4888
+ };
4889
+ }
4890
+ function deriveAuthPlan(tags, code, opts) {
4891
+ const t = Array.isArray(tags) ? tags : [];
4892
+ const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
4893
+ const isApi = t.includes("@api");
4894
+ const explicit = isApi ? explicitAuthTypeTags(t) : [];
4895
+ if (explicit.length > 0) {
4896
+ if (t.includes("@auth")) {
4897
+ return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
4898
+ }
4899
+ const loginCompose = explicit.filter((e) => e === AUTH_TAG_COOKIE_STATE || e === AUTH_TAG_LOGIN_COOKIE || e === AUTH_TAG_LOGIN_BEARER);
4900
+ if (explicit.length === 2 && explicit.includes(AUTH_TAG_STANDING) && loginCompose.length === 1) {
4901
+ const composedKeys = [...TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY];
4902
+ switch (loginCompose[0]) {
4903
+ case AUTH_TAG_COOKIE_STATE:
4904
+ return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "required", requiredEnvKeys: composedKeys });
4905
+ case AUTH_TAG_LOGIN_COOKIE:
4906
+ return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
4907
+ case AUTH_TAG_LOGIN_BEARER:
4908
+ return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
4909
+ }
4910
+ }
4911
+ if (explicit.length > 1) {
4912
+ return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
4913
+ }
4914
+ switch (explicit[0]) {
4915
+ case AUTH_TAG_UNSUPPORTED:
4916
+ return {
4917
+ ...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
4918
+ };
4919
+ case AUTH_TAG_NONE:
4920
+ return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
4921
+ case AUTH_TAG_STANDING:
4922
+ return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
4923
+ case AUTH_TAG_LOGIN_BEARER:
4924
+ return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
4925
+ case AUTH_TAG_LOGIN_COOKIE:
4926
+ return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
4927
+ case AUTH_TAG_COOKIE_STATE:
4928
+ return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
4929
+ }
4930
+ }
4931
+ const cookie = legacyNeedsCookieState(t, code, sharedFlag);
4932
+ const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
4933
+ const selfLoginWithoutStoredCreds = t.includes("@auth") && !t.includes("@needs-auth") && !isApi && !!code?.trim() && !/TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin\b/.test(code);
4934
+ const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
4935
+ const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
4936
+ return {
4937
+ version: 1,
4938
+ source: "legacy",
4939
+ acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
4940
+ presentation: authedLegacy ? "cookie" : "none",
4941
+ needsCookieState: cookie,
4942
+ needsStandingHeaders: false,
4943
+ // A legacy bare-@api test keeps injecting standing headers IF the project configured them
4944
+ // (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
4945
+ standingHeadersMode: isApi ? "legacy-if-configured" : "none",
4946
+ needsLoginCreds,
4947
+ requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
4948
+ };
4949
+ }
4950
+ function authTypeTag(tags) {
4951
+ const t = Array.isArray(tags) ? tags : [];
4952
+ const explicit = explicitAuthTypeTags(t);
4953
+ return explicit.length === 1 ? explicit[0] : void 0;
4954
+ }
4955
+ 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;
4956
+ var init_auth_plan = __esm({
4957
+ "../shared/dist/auth-plan.js"() {
4958
+ "use strict";
4959
+ AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
4960
+ AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
4961
+ AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
4962
+ AUTH_TAG_STANDING = "@auth-standing";
4963
+ AUTH_TAG_NONE = "@auth-none";
4964
+ AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
4965
+ AUTH_TYPE_TAGS = [
4966
+ AUTH_TAG_COOKIE_STATE,
4967
+ AUTH_TAG_LOGIN_COOKIE,
4968
+ AUTH_TAG_LOGIN_BEARER,
4969
+ AUTH_TAG_STANDING,
4970
+ AUTH_TAG_NONE,
4971
+ AUTH_TAG_UNSUPPORTED
4972
+ ];
4973
+ AUTH_TAG_ALIASES = {
4974
+ "@auth-cookie": AUTH_TAG_COOKIE_STATE,
4975
+ "@auth-bearer": AUTH_TAG_LOGIN_BEARER,
4976
+ "@auth-token": AUTH_TAG_LOGIN_BEARER,
4977
+ "@auth-api-key": AUTH_TAG_STANDING,
4978
+ "@auth-standing-header": AUTH_TAG_STANDING
4979
+ };
4980
+ TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
4981
+ API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
4982
+ }
4983
+ });
4984
+
4720
4985
  // ../shared/dist/assertion-strength.js
4986
+ function parseLastActionProofMode(raw) {
4987
+ const v = (raw ?? "").trim().toLowerCase();
4988
+ return v === "on" || v === "shadow" ? v : "off";
4989
+ }
4990
+ function isUrlProof(stmt) {
4991
+ if (URL_PROOF.test(stmt))
4992
+ return true;
4993
+ let calls = 0;
4994
+ for (const open of stmt.matchAll(POLL_OPEN)) {
4995
+ if (++calls > POLL_CALLS_MAX)
4996
+ break;
4997
+ const from = open.index + open[0].length;
4998
+ const args = balancedArguments(stmt, from, MATCHER_ARGS_MAX);
4999
+ if (args === null)
5000
+ continue;
5001
+ const subject = pollSubject(args);
5002
+ if (!POLL_PAGE_URL.test(subject))
5003
+ continue;
5004
+ if (!POLL_PAGE_URL_ONLY.test(subject) || PAGE_VALUE_MATCHER.test(stmt.slice(from + args.length)))
5005
+ return true;
5006
+ }
5007
+ return false;
5008
+ }
4721
5009
  function liveExpectStatements(uncommented) {
4722
5010
  const masked = maskedOutsideStrings(uncommented).split("\n");
4723
5011
  return accumulateStatements(uncommented.split("\n")).filter((s) => EXPECT_STMT.test(s.stmt) && EXPECT_STMT.test(masked[s.startLine - 1] ?? ""));
4724
5012
  }
4725
- function stripLineComment(ln) {
5013
+ function stripLineComment2(ln) {
4726
5014
  let quote = null;
4727
5015
  for (let i = 0; i < ln.length; i++) {
4728
5016
  const c = ln[i];
@@ -4745,7 +5033,7 @@ function splitInteractionStatementsWithLines(lines) {
4745
5033
  let depth = 0;
4746
5034
  let scaffold = false;
4747
5035
  for (let i = 0; i < lines.length; i++) {
4748
- const ln = stripLineComment(lines[i]).trim();
5036
+ const ln = stripLineComment2(lines[i]).trim();
4749
5037
  if (!ln)
4750
5038
  continue;
4751
5039
  const isContinuation = out.length > 0 && (ln.startsWith(".") || depth > 0 && !scaffold);
@@ -4769,7 +5057,7 @@ function testBlockIds(lines) {
4769
5057
  let any = false;
4770
5058
  let block = 0;
4771
5059
  for (let i = 0; i < lines.length; i++) {
4772
- const ln = stripLineComment(lines[i]).trim();
5060
+ const ln = stripLineComment2(lines[i]).trim();
4773
5061
  if (!open) {
4774
5062
  if (!TEST_BLOCK_OPEN.test(ln))
4775
5063
  continue;
@@ -4853,7 +5141,7 @@ function expectSubjectSpan(stmt) {
4853
5141
  }
4854
5142
  return null;
4855
5143
  }
4856
- function resolveSubjectBindings(stmts, bindings, blockOfLine) {
5144
+ function resolveSubjectBindings(stmts, bindings, blockOfLine, boundAt) {
4857
5145
  if (bindings.length === 0)
4858
5146
  return [...stmts];
4859
5147
  return stmts.map((item) => {
@@ -4876,14 +5164,22 @@ function resolveSubjectBindings(stmts, bindings, blockOfLine) {
4876
5164
  subject = `${lead[1]}${found.expr.replace(/;\s*$/, "")}${subject.slice(lead[0].length)}`;
4877
5165
  line = found.line;
4878
5166
  }
4879
- return { ...item, stmt: `${item.stmt.slice(0, span.from)}${subject}${item.stmt.slice(span.to)}` };
5167
+ const out = { ...item, stmt: `${item.stmt.slice(0, span.from)}${subject}${item.stmt.slice(span.to)}` };
5168
+ if (boundAt !== void 0 && line !== item.startLine)
5169
+ boundAt.set(out, line);
5170
+ return out;
4880
5171
  });
4881
5172
  }
4882
5173
  function stripLoginScaffolding(code) {
4883
- const out = [];
5174
+ const lines = code.split("\n");
5175
+ return keptScaffoldingLines(lines).map((i) => lines[i]).join("\n");
5176
+ }
5177
+ function keptScaffoldingLines(lines) {
5178
+ const kept = [];
4884
5179
  let inAuth = false;
4885
5180
  let inInbox = false;
4886
- for (const ln of code.split("\n")) {
5181
+ for (let i = 0; i < lines.length; i++) {
5182
+ const ln = lines[i];
4887
5183
  if (!inAuth && !inInbox && AUTH_START.test(ln)) {
4888
5184
  inAuth = true;
4889
5185
  continue;
@@ -4904,9 +5200,9 @@ function stripLoginScaffolding(code) {
4904
5200
  }
4905
5201
  if (LOGIN_CALL_LINE.test(ln))
4906
5202
  continue;
4907
- out.push(ln);
5203
+ kept.push(i);
4908
5204
  }
4909
- return out.join("\n");
5205
+ return kept;
4910
5206
  }
4911
5207
  function stripCommentsAndLogs(code) {
4912
5208
  return stripCoverageComments(code).split("\n").filter((ln) => !/^\s*console\./.test(ln)).join("\n");
@@ -4915,7 +5211,7 @@ function detectsErrorBannerAssertion(code) {
4915
5211
  const cleaned = stripCommentsAndLogs(stripLoginScaffolding(code));
4916
5212
  const lines = cleaned.split("\n");
4917
5213
  const candidates = [];
4918
- const positive = (stmt) => !NEGATED_ASSERT.test(stmt) && !ABSENCE_MATCHER.test(stmt);
5214
+ const positive = (stmt) => !claimsAbsence(stmt);
4919
5215
  const statements = liveExpectStatements(cleaned).map((s) => s.stmt);
4920
5216
  for (const stmt of statements) {
4921
5217
  if (positive(stmt))
@@ -5044,6 +5340,12 @@ function detectsObservedFailureAsExpected(code) {
5044
5340
  return FAILURE_STATUS_ASSERT.some((re) => re.test(stmt)) || hasFailedListener && FAILED_LIST_ASSERT.test(stmt);
5045
5341
  });
5046
5342
  }
5343
+ function claimsAbsence(stmt) {
5344
+ const negated = NEGATED_ASSERT_NOT.test(stmt);
5345
+ const bare = negated ? stmt.replace(NOT_BEFORE_MATCHER, ".") : stmt;
5346
+ const absenceMatcher = /\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/.test(bare) || VALUE_ABSENCE.test(bare) || OPTION_ABSENCE.test(bare);
5347
+ return negated !== absenceMatcher;
5348
+ }
5047
5349
  function isVacuousErrorAbsence(stmt, presence) {
5048
5350
  if (!ABSENCE_MATCHER.test(stmt))
5049
5351
  return false;
@@ -5052,6 +5354,41 @@ function isVacuousErrorAbsence(stmt, presence) {
5052
5354
  return false;
5053
5355
  return !presence().includes(m[1]);
5054
5356
  }
5357
+ function isVacuousAccountAbsence(stmt, shown, account) {
5358
+ if (!claimsAbsence(stmt))
5359
+ return false;
5360
+ if (!ABSENCE_MATCHER.test(stmt)) {
5361
+ const negated = NEGATED_TEXT.exec(stmt);
5362
+ if (!negated)
5363
+ return false;
5364
+ const read = account.exec(stmt.slice(negated.index));
5365
+ return read !== null && !shown().has(read[0]);
5366
+ }
5367
+ const m = account.exec(stmt);
5368
+ if (!m || shown().has(m[0]))
5369
+ return false;
5370
+ for (let at = endOfEnclosingCall(stmt, m.index + m[0].length); at < stmt.length; at = endOfEnclosingCall(stmt, at)) {
5371
+ SCOPING_STEP.lastIndex = at;
5372
+ if (SCOPING_STEP.test(stmt))
5373
+ return false;
5374
+ }
5375
+ return true;
5376
+ }
5377
+ function endOfEnclosingCall(stmt, from) {
5378
+ let depth = 0;
5379
+ for (let i = from; i < stmt.length; i++) {
5380
+ const c = stmt[i];
5381
+ if (c === "'" || c === '"') {
5382
+ for (i++; i < stmt.length && stmt[i] !== c; i++)
5383
+ if (stmt[i] === "\\")
5384
+ i++;
5385
+ } else if (c === "(")
5386
+ depth++;
5387
+ else if (c === ")" && depth-- === 0)
5388
+ return i + 1;
5389
+ }
5390
+ return stmt.length;
5391
+ }
5055
5392
  function isBareContainerVisibility(stmt) {
5056
5393
  return BARE_CONTAINER_ROLE.test(stmt) && !GET_BY_TEXT.test(stmt) && !HAS_TEXT_FILTER.test(stmt) && !TEXT_CONTENT_MATCHER.test(stmt);
5057
5394
  }
@@ -5059,19 +5396,53 @@ function isChromeTarget(stmt) {
5059
5396
  return CHROME_LOCATOR.test(stmt) && !CONTENT_ROLE.test(stmt) && !/getByText\s*\(/.test(stmt);
5060
5397
  }
5061
5398
  function countsAsContentText(stmt) {
5062
- if (!TEXT_CONTENT_MATCHER.test(stmt))
5399
+ if (!TEXT_CONTENT_MATCHER.test(stmt) || NEGATED_ASSERT.test(stmt))
5063
5400
  return false;
5064
5401
  if (isSymbolOnlyArgument(stmt, TEXT_CONTENT_MATCHER))
5065
5402
  return false;
5066
- if (!isChromeTarget(stmt))
5403
+ return onContentTarget(stmt);
5404
+ }
5405
+ function onContentTarget(stmt) {
5406
+ return !isChromeTarget(stmt) || /getByRole\s*\(\s*['"`]main['"`]/i.test(stmt);
5407
+ }
5408
+ function provesTextRemoved(stmt, presence) {
5409
+ const m = stmt.match(NEGATED_TEXT);
5410
+ if (!m || !onContentTarget(stmt))
5411
+ return false;
5412
+ const text = m[1] ?? m[2];
5413
+ if (text === void 0)
5067
5414
  return true;
5068
- return /getByRole\s*\(\s*['"`]main['"`]/i.test(stmt);
5415
+ if (!/[\p{L}\p{N}]/u.test(text))
5416
+ return false;
5417
+ return !ERROR_TEXT_WORDS.test(text) || presence().includes(text);
5069
5418
  }
5070
5419
  function isDataContentRole(stmt) {
5071
5420
  return CONTENT_ROLE.test(stmt) && !ANNOUNCE_ROLE.test(stmt);
5072
5421
  }
5422
+ function destructuresRequest(code) {
5423
+ let readTo = -1;
5424
+ for (const m of code.matchAll(ASYNC_DESTRUCTURE_OPEN)) {
5425
+ const start = m.index + m[0].length;
5426
+ if (start <= readTo)
5427
+ continue;
5428
+ let end = start;
5429
+ while (end < code.length && code[end] !== "}" && code[end] !== ")")
5430
+ end++;
5431
+ readTo = end;
5432
+ if (end === code.length)
5433
+ return false;
5434
+ if (code[end] !== "}" || !REQUEST_WORD.test(code.slice(start, end)))
5435
+ continue;
5436
+ let close = end + 1;
5437
+ while (close < code.length && /\s/.test(code[close]))
5438
+ close++;
5439
+ if (code[close] === ")")
5440
+ return true;
5441
+ }
5442
+ return false;
5443
+ }
5073
5444
  function isApiTest(code) {
5074
- const usesRequestFixture = /async\s*\(\s*\{[^})]*\brequest\b[^})]*\}\s*\)/.test(code) || /\brequest\.(get|post|put|patch|delete|head|fetch)\s*\(/.test(code);
5445
+ const usesRequestFixture = destructuresRequest(code) || /\brequest\.(get|post|put|patch|delete|head|fetch)\s*\(/.test(code);
5075
5446
  if (!usesRequestFixture)
5076
5447
  return false;
5077
5448
  const drivesUi = /\bpage\.(getBy\w+|locator|click|fill|goto|waitForURL|reload)\s*\(/.test(code) || /\.(click|fill|check|uncheck|selectOption|pressSequentially)\s*\(/.test(code);
@@ -5099,7 +5470,7 @@ function roleNames(s) {
5099
5470
  function interactedControlNames(lines) {
5100
5471
  const names = /* @__PURE__ */ new Set();
5101
5472
  for (const raw of lines) {
5102
- const ln = stripLineComment(raw);
5473
+ const ln = stripLineComment2(raw);
5103
5474
  if (!INTERACT_VERB.test(ln))
5104
5475
  continue;
5105
5476
  for (const n of roleNames(ln))
@@ -5295,6 +5666,14 @@ function balancedArguments(code, from, limit = code.length) {
5295
5666
  }
5296
5667
  return null;
5297
5668
  }
5669
+ function expectCallTail(stmt) {
5670
+ const at = stmt.search(EXPECT_CALL_RE);
5671
+ if (at < 0)
5672
+ return null;
5673
+ const from = stmt.indexOf("(", at) + 1;
5674
+ const args = balancedArguments(stmt, from, MATCHER_ARGS_MAX);
5675
+ return args === null ? null : stmt.slice(from + args.length).trimStart();
5676
+ }
5298
5677
  function matcherArgumentsAreConstant(body, from) {
5299
5678
  MATCHER_OPEN.lastIndex = from;
5300
5679
  const opened = MATCHER_OPEN.exec(body);
@@ -5330,32 +5709,70 @@ function hasOnlyConstantAssertions(code) {
5330
5709
  const { constant, real } = constantAssertions(code);
5331
5710
  return constant.length > 0 && real === 0;
5332
5711
  }
5712
+ function envValueKey(name) {
5713
+ return `\0env:${name}`;
5714
+ }
5333
5715
  function enteredValues(stmts) {
5334
5716
  const values = /* @__PURE__ */ new Map();
5335
5717
  const navLines = [];
5718
+ const accountConsts = /* @__PURE__ */ new Set();
5336
5719
  for (const { stmt, startLine } of stmts) {
5337
5720
  if (PAGE_NAV.test(stmt))
5338
5721
  navLines.push(startLine);
5722
+ for (const c of stmt.matchAll(ACCOUNT_CONST))
5723
+ accountConsts.add(c[1].replace(/\$/g, "\\$"));
5339
5724
  let m;
5340
5725
  ENTERED_VALUE.lastIndex = 0;
5341
- while (m = ENTERED_VALUE.exec(stmt)) {
5342
- const value = (m[1] ?? m[2] ?? m[3]).toLowerCase();
5343
- if (!values.has(value))
5344
- values.set(value, startLine);
5345
- }
5726
+ const add = (value) => {
5727
+ const lines = values.get(value);
5728
+ if (!lines)
5729
+ values.set(value, [startLine]);
5730
+ else if (lines[lines.length - 1] !== startLine)
5731
+ lines.push(startLine);
5732
+ };
5733
+ while (m = ENTERED_VALUE.exec(stmt))
5734
+ add((m[1] ?? m[2] ?? m[3]).toLowerCase());
5735
+ ENTERED_ENV.lastIndex = 0;
5736
+ while (m = ENTERED_ENV.exec(stmt))
5737
+ add(envValueKey(m[1] ?? m[2] ?? m[3] ?? m[4]));
5346
5738
  }
5347
- return { values, navLines };
5739
+ const consts = accountConsts.size > 0 ? `|(?<![\\w$.])(?:${[...accountConsts].join("|")})(?![\\w$])` : "";
5740
+ return { values, navLines, account: new RegExp(`${ACCOUNT_ENV}${consts}`) };
5348
5741
  }
5349
- function isEnteredValueEcho(stmt, entered, startLine) {
5742
+ function countBelow(lines, line) {
5743
+ let lo = 0;
5744
+ let hi = lines.length;
5745
+ while (lo < hi) {
5746
+ const mid = lo + hi >> 1;
5747
+ if (lines[mid] < line)
5748
+ lo = mid + 1;
5749
+ else
5750
+ hi = mid;
5751
+ }
5752
+ return lo;
5753
+ }
5754
+ function enteredLinesOf(stmt, entered) {
5350
5755
  const m = stmt.match(/\.toHaveValue\s*\(\s*(['"`])((?:(?!\1).)+)\1/);
5351
- if (!m)
5756
+ if (m)
5757
+ return entered.values.get(m[2].toLowerCase());
5758
+ const env = stmt.match(HAVE_VALUE_ENV);
5759
+ return env ? entered.values.get(envValueKey(env[1] ?? env[2])) : void 0;
5760
+ }
5761
+ function typedSinceLastNav(stmt, entered, startLine, lines = enteredLinesOf(stmt, entered)) {
5762
+ if (!lines || startLine === void 0)
5763
+ return false;
5764
+ const before = countBelow(lines, startLine);
5765
+ if (before === 0)
5352
5766
  return false;
5353
- const enteredAt = entered.values.get(m[2].toLowerCase());
5354
- if (enteredAt === void 0)
5767
+ return countBelow(entered.navLines, startLine) === countBelow(entered.navLines, lines[before - 1] + 1);
5768
+ }
5769
+ function isEnteredValueEcho(stmt, entered, startLine) {
5770
+ const lines = enteredLinesOf(stmt, entered);
5771
+ if (!lines)
5355
5772
  return false;
5356
- if (startLine === void 0)
5773
+ if (startLine === void 0 || lines[0] >= startLine)
5357
5774
  return true;
5358
- return !entered.navLines.some((n) => n > enteredAt && n < startLine);
5775
+ return typedSinceLastNav(stmt, entered, startLine, lines);
5359
5776
  }
5360
5777
  function isAnyValueMatcher(stmt) {
5361
5778
  if (/\.not\.toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/.test(stmt))
@@ -5445,6 +5862,49 @@ function hasAttributeTransition(stmts) {
5445
5862
  return true;
5446
5863
  return false;
5447
5864
  }
5865
+ function comparesWithItself(subject, tail) {
5866
+ const m = SELF_COMPARE_MATCHER.exec(tail);
5867
+ if (!m)
5868
+ return false;
5869
+ const args = balancedArguments(tail, m[0].length, MATCHER_ARGS_MAX);
5870
+ if (args === null)
5871
+ return false;
5872
+ const squash2 = (x) => x.replace(/^\s*await\s+/, "").replace(/\s+/g, "");
5873
+ const expected = squash2(args);
5874
+ return expected === squash2(m[1] === "toHaveCount" ? `${subject}.count()` : subject);
5875
+ }
5876
+ function asPlainAssertion(s) {
5877
+ return plainAssertion(s, false);
5878
+ }
5879
+ function plainAssertion(s, keepSelfCompare) {
5880
+ const { stmt } = s;
5881
+ if (COUNT_AT_LEAST_ZERO.test(stmt))
5882
+ return [];
5883
+ const subject = extractExpectSubject(stmt);
5884
+ if (subject === null)
5885
+ return [s];
5886
+ const plain = subject.replace(/^await\s+/, "").trim();
5887
+ const subjectAt = stmt.indexOf(subject, stmt.search(EXPECT_CALL_RE));
5888
+ const tail = (stmt.startsWith("expect.poll", stmt.search(EXPECT_CALL_RE)) ? expectCallTail(stmt) : null) ?? stmt.slice(subjectAt + subject.length).trimStart();
5889
+ const as = (rewritten) => [{ ...s, stmt: rewritten }];
5890
+ if (LOCATOR_EXPR.test(plain) && !LOCATOR_VALUE_READ.test(plain) && ALWAYS_TRUE_ON_OBJECT.test(tail))
5891
+ return [];
5892
+ if (DEFINED_VALUE_READ.test(plain) && ALWAYS_DEFINED.test(tail))
5893
+ return [];
5894
+ if (!keepSelfCompare && comparesWithItself(plain, tail))
5895
+ return [];
5896
+ const visible = /^(.+)\.\s*isVisible\s*\(\s*\)$/s.exec(plain);
5897
+ if (visible && IS_VISIBLE_TRUE.test(tail))
5898
+ return as(`await expect(${visible[1]}).toBeVisible();`);
5899
+ if (/^page\s*\.\s*(?:url|title)\s*\(\s*\)$/.test(plain) && PAGE_VALUE_MATCHER.test(tail)) {
5900
+ const matcher = plain.includes("url") ? "toHaveURL" : "toHaveTitle";
5901
+ return as(`await expect(page)${tail.replace(/^\)\s*\.\s*\w+\s*\(/, `.${matcher}(`)}`);
5902
+ }
5903
+ return [s];
5904
+ }
5905
+ function assertionStrengthHoistedReadsOn(value = typeof process !== "undefined" ? process.env?.ASSERTION_STRENGTH_HOISTED_READS : void 0) {
5906
+ return /^(1|true|on)$/i.test((value ?? "").trim());
5907
+ }
5448
5908
  function creditsOutcomeAlways(stmt, entered, startLine) {
5449
5909
  if (!OUTCOME_ALWAYS.some((re) => re.test(stmt)))
5450
5910
  return false;
@@ -5477,6 +5937,22 @@ function successNoticeQuote(stmt) {
5477
5937
  }
5478
5938
  return null;
5479
5939
  }
5940
+ function namedPagePath(arg) {
5941
+ const text = arg.trim();
5942
+ let path;
5943
+ const quoted = /^(['"`])([^'"`]*)\1$/.exec(text);
5944
+ const regex = quoted ? null : /^\/((?:\\.|[^/\\])+)\/[a-z]*$/.exec(text);
5945
+ if (quoted) {
5946
+ path = quoted[2].replace(/^\$\{[^}]*\}/, "").replace(/^\*\*/, "").replace(/^[a-z][a-z\d+.-]*:\/\/[^/]*/i, "").replace(/\*\*$/, "");
5947
+ } else if (regex) {
5948
+ path = regex[1].replace(/^\^/, "").replace(/^\.\*/, "").replace(/\$$/, "").replace(/\\([/.-])/g, "$1");
5949
+ } else {
5950
+ return null;
5951
+ }
5952
+ if (!PLAIN_PAGE_PATH.test(path))
5953
+ return null;
5954
+ return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
5955
+ }
5480
5956
  function provesStoredControlValue(stmt, startLine, persistence) {
5481
5957
  if (!persistence || startLine === void 0)
5482
5958
  return false;
@@ -5503,7 +5979,64 @@ function reopenedAfterSave(startLine, persistence, reopens = persistence.reloadL
5503
5979
  }
5504
5980
  return lo < reopens.length && reopens[lo] < startLine;
5505
5981
  }
5506
- function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine, persistence) {
5982
+ function actsBetween(withLines, from, to) {
5983
+ let acts = actLinesOf.get(withLines);
5984
+ if (acts === void 0) {
5985
+ acts = withLines.filter((s) => ACTS.test(s.stmt)).map((s) => s.startLine).sort((a, b) => a - b);
5986
+ actLinesOf.set(withLines, acts);
5987
+ }
5988
+ let lo = 0;
5989
+ let hi = acts.length;
5990
+ while (lo < hi) {
5991
+ const mid = lo + hi >> 1;
5992
+ if (acts[mid] <= from)
5993
+ lo = mid + 1;
5994
+ else
5995
+ hi = mid;
5996
+ }
5997
+ return lo < acts.length && acts[lo] < to;
5998
+ }
5999
+ function signInProofWindow(stmts, withLines, lines, blockIds) {
6000
+ if (blockIds === null)
6001
+ return void 0;
6002
+ const testBodies2 = /* @__PURE__ */ new Set();
6003
+ for (let i = 0; i < lines.length; i++) {
6004
+ const id = blockIds[i];
6005
+ if (id > 0 && (i === 0 || blockIds[i - 1] !== id) && TEST_BODY_OPEN.test(stripLineComment2(lines[i]).trim()))
6006
+ testBodies2.add(id);
6007
+ }
6008
+ const blockOf = (i) => blockIds[withLines[i].startLine - 1];
6009
+ const passwordAt = stmts.findIndex((stmt, i) => testBodies2.has(blockOf(i)) && ENV_PASSWORD_ENTRY.test(stmt));
6010
+ if (passwordAt < 0)
6011
+ return void 0;
6012
+ const body = blockOf(passwordAt);
6013
+ let clickAt = passwordAt + 1;
6014
+ while (clickAt < stmts.length && FORM_INPUT.test(stmts[clickAt]) && !ANY_CLICK.test(stmts[clickAt]))
6015
+ clickAt++;
6016
+ if (clickAt >= stmts.length || blockOf(clickAt) !== body || !ANY_CLICK.test(stmts[clickAt]) || NAV_LOCATOR.test(stmts[clickAt]))
6017
+ return void 0;
6018
+ let end = clickAt + 1;
6019
+ for (; end < stmts.length && blockOf(end) === body; end++) {
6020
+ if (CLOSERS_ONLY.test(stmts[end]))
6021
+ continue;
6022
+ if (!ASSERT_OR_WAIT.test(stmts[end]) || ACTS.test(stmts[end]))
6023
+ return void 0;
6024
+ }
6025
+ return { after: withLines[clickAt].startLine, until: end < stmts.length ? withLines[end].startLine : Number.POSITIVE_INFINITY };
6026
+ }
6027
+ function isAccountControlCheck(stmt) {
6028
+ if (!VISIBILITY_MATCHER.test(stmt) || NEGATED_ASSERT.test(stmt) || OPTION_ABSENCE.test(stmt))
6029
+ return false;
6030
+ const role = ACCOUNT_CONTROL_ROLE.exec(stmt);
6031
+ if (!role)
6032
+ return false;
6033
+ const rest = stmt.slice(role.index + role[0].length, role.index + role[0].length + 300);
6034
+ const close = rest.indexOf("}");
6035
+ const options = close < 0 ? rest : rest.slice(0, close);
6036
+ const name = options.match(/(?:^|[\s,])name\s*:\s*/);
6037
+ return name !== null && ACCOUNT_EMAIL_ENV.test(options.slice(name.index + name[0].length));
6038
+ }
6039
+ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine, persistence, signedIn) {
5507
6040
  if (mutated && hasAttributeTransition(stmts))
5508
6041
  return { kind: "durable" };
5509
6042
  const notices = [];
@@ -5513,11 +6046,25 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
5513
6046
  };
5514
6047
  let presenceText;
5515
6048
  const presence = () => presenceText ??= stmts.filter((o) => !ABSENCE_MATCHER.test(o.stmt) && !NEGATED_ASSERT.test(o.stmt)).map((o) => o.stmt).join("\n");
6049
+ let shownAccounts;
6050
+ const shown = () => {
6051
+ if (shownAccounts)
6052
+ return shownAccounts;
6053
+ const ref = new RegExp(entered.account.source, "g");
6054
+ return shownAccounts = new Set(stmts.filter((o) => !claimsAbsence(o.stmt)).flatMap((o) => Array.from(o.stmt.matchAll(ref), (m) => m[0])));
6055
+ };
5516
6056
  for (const { stmt, startLine } of stmts) {
5517
6057
  if (firstMutLine !== void 0 && startLine !== void 0 && startLine < firstMutLine)
5518
6058
  continue;
5519
6059
  if (isVacuousErrorAbsence(stmt, presence))
5520
6060
  continue;
6061
+ if (COUNT_ONE.test(stmt) && quotedAssertedStrings(stmt).some((t) => clicked.has(t.toLowerCase())))
6062
+ continue;
6063
+ if (isVacuousAccountAbsence(stmt, shown, entered.account))
6064
+ continue;
6065
+ if (signedIn && startLine !== void 0 && startLine > signedIn.after && startLine < signedIn.until && isAccountControlCheck(stmt)) {
6066
+ return { kind: "durable" };
6067
+ }
5521
6068
  if (creditsOutcomeAlways(stmt, entered, startLine))
5522
6069
  return { kind: "durable" };
5523
6070
  if (mutated && !NEGATED_ASSERT.test(stmt) && MESSAGE_SURFACE.test(stmt) && OUTCOME_TEXT.test(assertedText(stmt))) {
@@ -5527,12 +6074,15 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
5527
6074
  else
5528
6075
  return { kind: "durable" };
5529
6076
  }
5530
- if (STATE_MATCHER.test(stmt) && (persistence && startLine !== void 0 ? reopenedAfterSave(startLine, persistence, persistence.reloadCalls) : reloaded))
6077
+ const retyped = typedSinceLastNav(stmt, entered, startLine);
6078
+ if (!retyped && STATE_MATCHER.test(stmt) && (persistence && startLine !== void 0 ? reopenedAfterSave(startLine, persistence, persistence.reloadCalls) : reloaded))
5531
6079
  return { kind: "durable" };
5532
- if (provesStoredControlValue(stmt, startLine, persistence))
6080
+ if (!retyped && provesStoredControlValue(stmt, startLine, persistence))
5533
6081
  return { kind: "durable" };
5534
6082
  if (countsAsContentText(stmt))
5535
6083
  return { kind: "durable" };
6084
+ if (provesTextRemoved(stmt, presence))
6085
+ return { kind: "durable" };
5536
6086
  if (mutated && VISIBILITY_MATCHER.test(stmt)) {
5537
6087
  const revealsText = GET_BY_TEXT.test(stmt) || HAS_TEXT_FILTER.test(stmt) || CONTENT_ROLE.test(stmt);
5538
6088
  if (revealsText && !isChromeTarget(stmt)) {
@@ -5596,10 +6146,57 @@ function noticeOnlyExemption(clicked, noticeText, interactionStmts) {
5596
6146
  function detectOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine) {
5597
6147
  return classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine).kind !== "none";
5598
6148
  }
6149
+ function clickLabel(stmt) {
6150
+ let label = null;
6151
+ for (const m of stmt.matchAll(CONTROL_LABEL))
6152
+ label = m[2] ?? m[3] ?? null;
6153
+ return label;
6154
+ }
6155
+ function teardownLines(lines) {
6156
+ const out = new Array(lines.length).fill(false);
6157
+ let open = false;
6158
+ let depth = 0;
6159
+ for (let i = 0; i < lines.length; i++) {
6160
+ const ln = stripLineComment2(lines[i]).trim();
6161
+ let from = 0;
6162
+ if (!open) {
6163
+ const m = TEARDOWN_OPEN.exec(ln);
6164
+ if (!m)
6165
+ continue;
6166
+ open = true;
6167
+ depth = 0;
6168
+ from = m.index;
6169
+ }
6170
+ out[i] = true;
6171
+ let closed = false;
6172
+ let quote = null;
6173
+ for (let k = from; k < ln.length; k++) {
6174
+ const ch = ln[k];
6175
+ if (quote !== null) {
6176
+ if (ch === "\\")
6177
+ k++;
6178
+ else if (ch === quote)
6179
+ quote = null;
6180
+ continue;
6181
+ }
6182
+ if (ch === "'" || ch === '"' || ch === "`")
6183
+ quote = ch;
6184
+ else if (ch === "{")
6185
+ depth++;
6186
+ else if (ch === "}") {
6187
+ depth--;
6188
+ closed = true;
6189
+ }
6190
+ }
6191
+ if (depth <= 0 && closed)
6192
+ open = false;
6193
+ }
6194
+ return out;
6195
+ }
5599
6196
  function validateAssertionStrength(code, opts = {}) {
5600
6197
  const body = stripLoginScaffolding(code);
5601
6198
  const lines = body.split("\n");
5602
- const stmts = liveExpectStatements(stripCoverageComments(body));
6199
+ const stmts = liveExpectStatements(stripCoverageComments(body)).flatMap(asPlainAssertion);
5603
6200
  const base = (extra) => ({
5604
6201
  apiTest: false,
5605
6202
  interacted: false,
@@ -5639,7 +6236,13 @@ function validateAssertionStrength(code, opts = {}) {
5639
6236
  const interactionStmts = inlineLocatorBindings(interactionStmtsRaw);
5640
6237
  const blockIds = testBlockIds(lines);
5641
6238
  const blockOfLine = (line) => blockIds?.[line - 1] ?? 0;
5642
- const checks = resolveSubjectBindings(stmts, scopedLocatorBindings(interactionStmtsWithLines, blockOfLine), blockOfLine);
6239
+ const hoistedReads = assertionStrengthHoistedReadsOn();
6240
+ const boundAt = hoistedReads ? /* @__PURE__ */ new Map() : void 0;
6241
+ const resolved = resolveSubjectBindings(stmts, scopedLocatorBindings(interactionStmtsWithLines, blockOfLine), blockOfLine, boundAt);
6242
+ const checks = boundAt === void 0 ? resolved : resolved.flatMap((s) => {
6243
+ const from = boundAt.get(s);
6244
+ return plainAssertion(s, from !== void 0 && actsBetween(interactionStmtsWithLines, from, s.startLine));
6245
+ });
5643
6246
  const insideTest = linesInsideTestBlocks(lines);
5644
6247
  const inTestOrder = (i) => insideTest === null || insideTest[interactionStmtsWithLines[i].startLine - 1] === true;
5645
6248
  let mutated = opts.interactionPerformed === true;
@@ -5649,9 +6252,34 @@ function validateAssertionStrength(code, opts = {}) {
5649
6252
  let navClicks = 0;
5650
6253
  const persistence = { mutLines: [], reloadLines: [], reloadCalls: [] };
5651
6254
  const openedPages = /* @__PURE__ */ new Set();
6255
+ let checkedPage = null;
6256
+ const savedOnCheckedPage = /* @__PURE__ */ new Set();
6257
+ const saveAt = (i, at) => {
6258
+ lastMutIdx = i;
6259
+ if (!inTestOrder(i))
6260
+ return;
6261
+ persistence.mutLines.push(at);
6262
+ if (firstMutIdx < 0)
6263
+ firstMutIdx = i;
6264
+ if (checkedPage !== null)
6265
+ savedOnCheckedPage.add(checkedPage);
6266
+ };
6267
+ const lastActionProof = opts.lastActionProof ?? "off";
6268
+ const teardown = lastActionProof === "off" ? null : teardownLines(lines);
6269
+ const acts = { last: null, prev: null, count: 0 };
6270
+ const noteAct = (i, at, kind) => {
6271
+ if (teardown === null || teardown[at - 1] || !inTestOrder(i))
6272
+ return;
6273
+ if (kind !== "enter")
6274
+ acts.count++;
6275
+ acts.prev = acts.last;
6276
+ acts.last = { i, kind };
6277
+ };
5652
6278
  for (let i = 0; i < interactionStmts.length; i++) {
5653
6279
  const ln = interactionStmts[i];
5654
6280
  const at = interactionStmtsWithLines[i].startLine;
6281
+ if (teardown !== null && acts.last?.kind === "fill" && ENTER_PRESS.test(ln))
6282
+ noteAct(i, at, "enter");
5655
6283
  if (RELOAD.test(ln)) {
5656
6284
  persistence.reloadLines.push(at);
5657
6285
  persistence.reloadCalls.push(at);
@@ -5659,32 +6287,31 @@ function validateAssertionStrength(code, opts = {}) {
5659
6287
  const opened = ln.match(/\.goto\s*\(\s*([^,)]+)/);
5660
6288
  if (opened) {
5661
6289
  const page = opened[1].trim();
5662
- if (openedPages.has(page))
6290
+ const path = namedPagePath(page);
6291
+ if (openedPages.has(page) || path !== null && savedOnCheckedPage.has(path))
5663
6292
  persistence.reloadLines.push(at);
5664
- else
5665
- openedPages.add(page);
5666
- }
6293
+ openedPages.add(page);
6294
+ checkedPage = null;
6295
+ }
6296
+ if (/\bpage\s*\.\s*go(?:Back|Forward)\s*\(/.test(ln))
6297
+ checkedPage = null;
6298
+ const checked = URL_CHECK_ARG.exec(ln);
6299
+ if (checked)
6300
+ checkedPage = namedPagePath(checked[1] ?? checked[2]);
5667
6301
  if (FORM_INPUT.test(ln)) {
5668
6302
  mutated = true;
5669
- lastMutIdx = i;
5670
- if (inTestOrder(i)) {
5671
- persistence.mutLines.push(at);
5672
- if (firstMutIdx < 0)
5673
- firstMutIdx = i;
5674
- }
6303
+ saveAt(i, at);
6304
+ noteAct(i, at, "fill");
5675
6305
  continue;
5676
6306
  }
5677
6307
  if (ANY_CLICK.test(ln)) {
5678
6308
  if (NAV_LOCATOR.test(ln)) {
5679
6309
  navClicks++;
6310
+ checkedPage = null;
5680
6311
  } else {
5681
6312
  mutated = true;
5682
- lastMutIdx = i;
5683
- if (inTestOrder(i)) {
5684
- persistence.mutLines.push(at);
5685
- if (firstMutIdx < 0)
5686
- firstMutIdx = i;
5687
- }
6313
+ saveAt(i, at);
6314
+ noteAct(i, at, "click");
5688
6315
  }
5689
6316
  }
5690
6317
  }
@@ -5717,9 +6344,28 @@ function validateAssertionStrength(code, opts = {}) {
5717
6344
  };
5718
6345
  }
5719
6346
  const orderedPersistence = firstMutLine !== void 0 ? persistence : void 0;
5720
- const proof = classifyOutcomeProof(checks, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence);
6347
+ const signedIn = signInProofWindow(interactionStmts, interactionStmtsWithLines, lines, blockIds);
6348
+ const proof = classifyOutcomeProof(checks, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence, signedIn);
5721
6349
  if (proof.kind === "durable") {
5722
6350
  const signals2 = base({ interacted, mutated, navOnly, hasOutcomeProof: true, terminal });
6351
+ const { last, prev } = acts;
6352
+ 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])))) {
6353
+ const lastLine = interactionStmtsWithLines[last.i].startLine;
6354
+ const clicked = clickedControlNames(interactionStmts);
6355
+ const after = classifyOutcomeProof(checks, mutated, reloaded, clicked, entered, lastLine, orderedPersistence, signedIn);
6356
+ const unproven = after.kind === "none" || after.kind === "notice" && noticeOnlyExemption(clicked, after.noticeText, interactionStmts) === null;
6357
+ if (unproven && lastActionProof === "on") {
6358
+ const line = (keptScaffoldingLines(code.split("\n"))[lastLine - 1] ?? lastLine - 1) + 1;
6359
+ return {
6360
+ ok: false,
6361
+ severity: "block",
6362
+ reason: `the last save (line ${line}) has no outcome check after it (the earlier check proves only the earlier action)`,
6363
+ signals: { ...signals2, lastActionUnproven: true }
6364
+ };
6365
+ }
6366
+ if (unproven)
6367
+ signals2.lastActionUnproven = true;
6368
+ }
5723
6369
  return { ok: true, severity: "none", reason: "proves an outcome (count / diff / value / revealed content)", signals: signals2 };
5724
6370
  }
5725
6371
  if (mutated && proof.kind === "notice") {
@@ -5768,7 +6414,7 @@ function validateAssertionStrength(code, opts = {}) {
5768
6414
  signals
5769
6415
  };
5770
6416
  }
5771
- const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && URL_PROOF.test(s));
6417
+ const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && isUrlProof(s));
5772
6418
  if (urlProofAfter) {
5773
6419
  return {
5774
6420
  ok: false,
@@ -5805,7 +6451,7 @@ function validateAssertionStrength(code, opts = {}) {
5805
6451
  signals
5806
6452
  };
5807
6453
  }
5808
- 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, 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, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, 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, IN_FLIGHT_TEXT, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD;
6454
+ 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;
5809
6455
  var init_assertion_strength = __esm({
5810
6456
  "../shared/dist/assertion-strength.js"() {
5811
6457
  "use strict";
@@ -5814,6 +6460,7 @@ var init_assertion_strength = __esm({
5814
6460
  init_assertion_proof();
5815
6461
  init_auth_handoff_coverage();
5816
6462
  init_expect_call();
6463
+ init_auth_plan();
5817
6464
  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;
5818
6465
  OUTCOME_ALWAYS = [
5819
6466
  /\.toHaveCount\s*\(/,
@@ -5830,8 +6477,8 @@ var init_assertion_strength = __esm({
5830
6477
  // form field value
5831
6478
  /\.toMatch\s*\(/,
5832
6479
  // string/regex content match (e.g. textContent() vs /[2-9] selected/)
5833
- /\.toBeDisabled\s*\(/,
5834
- // a control became DISABLED — a gating/validation outcome
6480
+ /\.(?<!\.not\s*\.)\s*toBeDisabled\s*\(/,
6481
+ // a control became DISABLED — a gating/validation outcome (not.toBeDisabled is the toBeEnabled twin)
5835
6482
  /\.not\.toBeVisible\s*\(/,
5836
6483
  /\.toBeHidden\s*\(/
5837
6484
  // something the action removed/closed (disappearance proof)
@@ -5839,12 +6486,16 @@ var init_assertion_strength = __esm({
5839
6486
  FORM_INPUT = /\.(fill|selectOption|setInputFiles|check|uncheck|setChecked|pressSequentially)\s*\(|keyboard\.type\s*\(/;
5840
6487
  ANY_CLICK = /\.click\s*\(/;
5841
6488
  NAV_LOCATOR = /getByRole\s*\(\s*['"`](?:link|menuitem)['"`]/i;
5842
- URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(/;
6489
+ 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*\(/;
6490
+ POLL_OPEN = /\bexpect\s*\.\s*poll\s*\(/g;
6491
+ POLL_PAGE_URL = /^page\s*\.\s*url\s*\(\s*\)/;
6492
+ POLL_PAGE_URL_ONLY = /^page\s*\.\s*url\s*\(\s*\)$/;
6493
+ POLL_CALLS_MAX = 8;
5843
6494
  RELOAD = /\.reload\s*\(/;
5844
6495
  EXPECT_STMT = /\bexpect\s*[.(]/;
5845
6496
  BLOCK_SCAFFOLD2 = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
5846
6497
  TEST_BLOCK_OPEN = /^(?:test|it)(?:\.(?:only|skip|fixme|slow|serial|describe\.serial))?\s*\(|^(?:test\.)?(?:beforeEach|beforeAll)\s*\(/;
5847
- LOCATOR_BINDING = /^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(.+?(?:getByRole|getByText|getByLabel|getByPlaceholder|getByTestId|getByAltText|getByTitle|locator)\s*\(.+)$/;
6498
+ 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*\(.+)$/;
5848
6499
  EXPECT_OPEN = /\bexpect(?:\.soft)?\s*\(/;
5849
6500
  STATE_MATCHER = /\.(toBeChecked|toBeVisible|toHaveValue|toBeDisabled|toBeEnabled)\s*\(/;
5850
6501
  CHROME_LOCATOR = /getByRole\s*\(\s*['"`](?:heading|navigation|banner|main|contentinfo|complementary|region|search|tablist|tab|button|link|menuitem|textbox|searchbox|combobox|spinbutton)['"`]/i;
@@ -5864,16 +6515,17 @@ var init_assertion_strength = __esm({
5864
6515
  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;
5865
6516
  AUTH_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_AUTH/;
5866
6517
  AUTH_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_AUTH/;
5867
- LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*;?\s*$/;
6518
+ LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*(?:;\s*)?$/;
5868
6519
  INBOX_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_INBOX/;
5869
6520
  INBOX_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_INBOX/;
5870
6521
  NEGATED_ASSERT = /\.not\s*\.\s*to[A-Z]\w*|\.toBeHidden\s*\(/;
6522
+ NEGATED_ASSERT_NOT = /\.not\s*\.\s*to[A-Z]/;
5871
6523
  CONST_CHAIN_MAX_DEPTH = 5;
5872
6524
  TYPED_INPUT = /\.(fill|type|pressSequentially|setInputFiles)\s*\(/;
5873
6525
  TITLE_LINE = /^\s*(?:test|describe)(?:\.\w+)?\s*\(/;
5874
6526
  FAILURE_STATUS_ASSERT = [
5875
6527
  /\.status\s*\(\s*\)[^;]{0,200}\.(?:toBeGreaterThanOrEqual|toBeGreaterThan)\s*\(\s*(?:399|[45]\d\d)\b/,
5876
- /\bexpect\s*\(\s*[^;]{0,200}\bstatus\s*(?:\(\s*\))?\s*>=?\s*(?:399|[45]\d\d)\b[^;]{0,200}\)\s*\.\s*(?:toBe\s*\(\s*true|toBeTruthy)/,
6528
+ /\bexpect\s*\(\s*[^;]{0,200}\bstatus\s*(?:\(\s*\)\s*)?>=?\s*(?:399|[45]\d\d)\b[^;]{0,200}\)\s*\.\s*(?:toBe\s*\(\s*true|toBeTruthy)/,
5877
6529
  /\bexpect\s*\([^;]{0,200}\.ok\s*\(\s*\)\s*\)\s*\.\s*(?:toBe\s*\(\s*false\s*\)|toBeFalsy\s*\()/
5878
6530
  ];
5879
6531
  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;
@@ -5883,8 +6535,15 @@ var init_assertion_strength = __esm({
5883
6535
  CATCH_ALL_PATTERN = /^(['"`])\*\*(?:\/\*)?\1$|^\/\.\*\/[a-z]*$/;
5884
6536
  GUARD_HANDLER = /\bMUTATING\.has\s*\(|blockedbyclient|Read-only guard/;
5885
6537
  ABSENCE_MATCHER = /\.not\.toBeVisible\s*\(|\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/;
6538
+ VALUE_ABSENCE = /\.(?:count\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*0\s*\)|toBeLessThan\s*\(\s*1\s*\))|isVisible\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*false\s*\)|toBeFalsy\s*\()|isHidden\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*true\s*\)|toBeTruthy\s*\())/;
6539
+ OPTION_ABSENCE = /\.(?:toBeVisible\s*\(\s*\{[^{}()]{0,200}\bvisible|toBeAttached\s*\(\s*\{[^{}()]{0,200}\battached)\s*:\s*false\b/;
6540
+ NOT_BEFORE_MATCHER = /\.not\s*\.\s*(?=to[A-Z])/g;
5886
6541
  ERROR_TEXT_WORDS = /error|fail|wrong|try again|unable|could ?n.?t|can.?t|problem|invalid|unavailable/i;
5887
6542
  TEXT_REGEX_ARG = /(?:getByText|hasText|getByRole\([^)]{0,200}name)\s*[:(]\s*(\/(?:\\.|[^/\\\n])+\/[a-z]*)/;
6543
+ SCOPING_STEP = /(?:\s*\.\s*(?:first|last|nth)\s*\([^()]*\))*\s*\.\s*(?:getBy[A-Z]\w*|locator|filter)\s*\(/y;
6544
+ NEGATED_TEXT = /\.not\s*\.\s*(?:toHaveText|toContainText)\s*\(\s*(?:['"`]([^'"`]*)['"`]|\/((?:\\.|[^/\\\n])+)\/)?/;
6545
+ ASYNC_DESTRUCTURE_OPEN = /async\s*\(\s*\{/g;
6546
+ REQUEST_WORD = /\brequest\b/;
5888
6547
  ROLE_NAME_RE = /getByRole\s*\(\s*['"`][a-z]+['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
5889
6548
  INTERACT_VERB = /\.(click|fill|check|uncheck|setChecked|selectOption|pressSequentially)\s*\(/;
5890
6549
  ROLE_SIG_RE = /getByRole\s*\(\s*['"`]([a-z]+)['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
@@ -5905,21 +6564,64 @@ var init_assertion_strength = __esm({
5905
6564
  MATCHER_OPEN = /\s*(?:\.\s*(?:not|resolves|rejects)\s*)*\.\s*[A-Za-z_$][\w$]*\s*\(/y;
5906
6565
  ENTERED_VALUE = /\.(?:fill|pressSequentially)\s*\(\s*['"`]([^'"`]+)['"`]|keyboard\.type\s*\(\s*['"`]([^'"`]+)['"`]|\.selectOption\s*\(\s*(?:\[\s*)?(?:\{\s*(?:label|value)\s*:\s*)?['"`]([^'"`]+)['"`]/g;
5907
6566
  PAGE_NAV = /\.(?:goto|reload)\s*\(/;
6567
+ ENV_READ = String.raw`process\.env(?:(?:\?\.|\.)([A-Za-z_]\w*)|(?:\?\.)?\[\s*['"]([A-Za-z_]\w*)['"]\s*\])`;
6568
+ ENTERED_ENV = new RegExp(String.raw`\.(?:fill|pressSequentially)\s*\(\s*${ENV_READ}|keyboard\.type\s*\(\s*${ENV_READ}`, "g");
6569
+ HAVE_VALUE_ENV = new RegExp(String.raw`\.toHaveValue\s*\(\s*${ENV_READ}`);
6570
+ ACCOUNT_KEYS = TEST_USER_ENV_KEYS.join("|");
6571
+ ACCOUNT_ENV = `process\\.env(?:\\??\\.(?:${ACCOUNT_KEYS})\\b|(?:\\?\\.)?\\[\\s*['"\`](?:${ACCOUNT_KEYS})['"\`]\\s*\\])`;
6572
+ ACCOUNT_CONST = new RegExp(`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*(?::\\s*string\\s*)?=\\s*${ACCOUNT_ENV}`, "g");
5908
6573
  IN_FLIGHT_TEXT = /^(?:(?:sending|saving|loading|searching|checking|processing|submitting|preparing|updating|please wait|working|one moment)\b[^.]{0,40}\.{2,3}|\.{3}|…)$/i;
6574
+ LOCATOR_VALUE_READ = /\.(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts|evaluate|evaluateAll|boundingBox|all)\s*\(/;
6575
+ LOCATOR_EXPR = /^(?:this\.)?page\s*\.\s*(?:getBy\w+|locator)\s*\(/;
6576
+ ALWAYS_TRUE_ON_OBJECT = /^\)\s*\.\s*(?:toBeTruthy|toBeDefined|not\s*\.\s*(?:toBeNull|toBeUndefined|toBeFalsy))\s*\(/;
6577
+ COUNT_AT_LEAST_ZERO = /\.(?:count\s*\(\s*\)|length)[\s)]{0,10}\.(?:toBeGreaterThanOrEqual\s*\(\s*0\s*\)|toBeGreaterThan\s*\(\s*-\s*1\s*\))/;
6578
+ DEFINED_VALUE_READ = /\.\s*(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|title|url|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts)\s*\([^()]*\)$/;
6579
+ ALWAYS_DEFINED = /^\)\s*\.\s*(?:toBeDefined|not\s*\.\s*toBeUndefined)\s*\(/;
6580
+ SELF_COMPARE_MATCHER = /^\)\s*\.\s*(toBe|toEqual|toStrictEqual|toHaveCount)\s*\(/;
6581
+ IS_VISIBLE_TRUE = /^\)*\s*\.\s*(?:toBe\s*\(\s*true\s*\)|toBeTruthy\s*\(\s*\))/;
6582
+ PAGE_VALUE_MATCHER = /^\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
6583
+ COUNT_ONE = /\.\s*toHaveCount\s*\(\s*1\s*\)/;
6584
+ URL_CHECK_ARG = /\bpage\s*\.\s*waitForURL\s*\(\s*([^,)]+)|\bexpect(?:\s*\.\s*soft)?\s*\(\s*page\s*\)\s*\.\s*toHaveURL\s*\(\s*([^,)]+)/;
6585
+ PLAIN_PAGE_PATH = /^\/[\w\-./~%]*(?:[?#][^\s]*)?$/;
5909
6586
  FORM_CONTROL_LOCATOR = /getByRole\s*\(\s*['"`](?:combobox|switch|checkbox|radio|textbox|searchbox|spinbutton|slider)['"`]|getByLabel\s*\(|getByPlaceholder\s*\(/;
5910
6587
  CONTROL_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(|\.toHaveAttribute\s*\(\s*['"`]aria-checked['"`]/;
6588
+ ACCOUNT_EMAIL_ENV = /^process\.env(?:(?:\?\.|\.)TEST_USER_EMAIL\b|(?:\?\.)?\[\s*['"]TEST_USER_EMAIL['"]\s*\])/;
6589
+ ENV_PASSWORD_ENTRY = /\.(?:fill|pressSequentially)\s*\(\s*process\.env(?:(?:\?\.|\.)TEST_USER_PASSWORD\b|(?:\?\.)?\[\s*['"]TEST_USER_PASSWORD['"]\s*\])/;
6590
+ ACCOUNT_CONTROL_ROLE = /getByRole\s*\(\s*['"`](?:button|link|menuitem)['"`]\s*,\s*\{/;
6591
+ ASSERT_OR_WAIT = /^(?:await\s+)?(?:expect(?:\.soft|\.poll)?\s*\(|[\w$]+(?:\.[\w$]+)*\.waitFor[A-Za-z]*\s*\()/;
6592
+ 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*\(/;
6593
+ actLinesOf = /* @__PURE__ */ new WeakMap();
6594
+ CLOSERS_ONLY = /^[})\];,\s]+$/;
6595
+ TEST_BODY_OPEN = /^(?:test|it)(?:\.(?:only|skip|fixme|slow))?\s*\(/;
5911
6596
  COPY_CONTROL = /\bcopy\b/i;
5912
6597
  COPIED_NOTICE = /\bcopied\b/i;
5913
6598
  SEND_CONTROL = /\b(send|submit|subscribe|sign up for|contact|feedback|join (the )?waitlist)\b/i;
5914
6599
  SEND_NOTICE = /\b(thank|thanks|sent|subscribed|received|in touch)\b/i;
5915
6600
  MARK_READ_CONTROL = /\bmark(?:ed)? as read\b|\bmark all(?: as)? read\b/i;
5916
6601
  APP_DATA_FIELD = /\b(title|name of|amount|price|quantity|sku|status|due|assignee|project|ticket|invoice|order)\b/i;
6602
+ COMMIT_CONTROL = /\b(?:save|submit|update|apply|add|create|post|send|publish)\b|\bconfirm\s+changes\b/i;
6603
+ 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;
6604
+ DESTRUCTIVE_CONTROL = /\b(?:delete|remove|discard)\b/i;
6605
+ ENTER_PRESS = /\.press\s*\(\s*['"`]Enter['"`]/;
6606
+ CONTROL_LABEL = /(?:name\s*:\s*|getBy(?:Text|Label|Title|TestId)\s*\(\s*|has-text\(\s*)(?:(['"`])((?:(?!\1).)*)\1|\/((?:\\.|[^/\\])+)\/)/g;
6607
+ isCommitClick = (stmt) => {
6608
+ const label = clickLabel(stmt);
6609
+ return label !== null && COMMIT_CONTROL.test(label) && !NOT_COMMIT_CONTROL.test(label) && !EXCLUDE_AFFORDANCE.test(label);
6610
+ };
6611
+ isDestructiveClick = (stmt) => {
6612
+ const label = clickLabel(stmt);
6613
+ return label !== null && DESTRUCTIVE_CONTROL.test(label);
6614
+ };
6615
+ TEARDOWN_OPEN = /\bfinally\s*\{|^(?:test\.)?(?:after|before)(?:Each|All)\s*\(/;
5917
6616
  }
5918
6617
  });
5919
6618
 
5920
6619
  // ../shared/dist/run-classification.js
5921
- function isUserCancelledError(error) {
5922
- return typeof error === "string" && error.trim().startsWith(USER_CANCELLED_RUN_ERROR);
6620
+ function isCancelledReason(error) {
6621
+ if (typeof error !== "string")
6622
+ return false;
6623
+ const text = error.trim();
6624
+ return CANCELLED_REASON_PREFIXES.some((prefix) => text.startsWith(prefix));
5923
6625
  }
5924
6626
  function isHeldHealRun(run) {
5925
6627
  return run?.status === "PASSED" && typeof run.error === "string" && run.error.startsWith(HEAL_PATCH_HELD_RUN_ERROR_PREFIX);
@@ -5952,7 +6654,7 @@ function pickLatestHealthRun(runs) {
5952
6654
  for (const run of runs) {
5953
6655
  if (run.mode === "auth-setup")
5954
6656
  continue;
5955
- if (run.status === "ERROR" && isUserCancelledError(run.error))
6657
+ if (run.status === "ERROR" && isCancelledReason(run.error))
5956
6658
  continue;
5957
6659
  if (isHeldHealRun(run))
5958
6660
  continue;
@@ -5970,6 +6672,15 @@ function pickDisplayRun(runs) {
5970
6672
  return latest;
5971
6673
  return pickLatestHealthRun(runs) ?? latest;
5972
6674
  }
6675
+ function featureRowRunStatus(runs) {
6676
+ const latest = runs.find((run) => run.mode !== "auth-setup");
6677
+ if (!latest)
6678
+ return null;
6679
+ const decisive = pickLatestHealthRun(runs.filter((run) => !isEnvironmentalError(run)));
6680
+ if (decisive)
6681
+ return decisive.status;
6682
+ return latest.status === "PENDING" || latest.status === "RUNNING" ? latest.status : null;
6683
+ }
5973
6684
  function isEnvironmentalError(run) {
5974
6685
  if (!run)
5975
6686
  return false;
@@ -6001,7 +6712,7 @@ function classifyErrorMessage(error) {
6001
6712
  return "config_issue";
6002
6713
  if (EMAIL_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
6003
6714
  return "email_not_received";
6004
- if (isUserCancelledError(error))
6715
+ if (isCancelledReason(error))
6005
6716
  return "cancelled";
6006
6717
  if (isRetriedRunInterruption(error))
6007
6718
  return "interrupted";
@@ -6036,7 +6747,7 @@ function heldHealRunWhere() {
6036
6747
  function excludeCancelledRunsWhere() {
6037
6748
  return {
6038
6749
  NOT: [
6039
- { status: "ERROR", error: { not: null, startsWith: USER_CANCELLED_RUN_ERROR } },
6750
+ { status: "ERROR", OR: CANCELLED_REASON_PREFIXES.map((prefix) => ({ error: { not: null, startsWith: prefix } })) },
6040
6751
  heldHealRunWhere()
6041
6752
  ]
6042
6753
  };
@@ -6114,11 +6825,25 @@ function reportedBugWhere() {
6114
6825
  function bugReviewStatus(status) {
6115
6826
  return status === "QUARANTINED" ? "confirmed" : "needs-review";
6116
6827
  }
6117
- 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, 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;
6828
+ 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;
6118
6829
  var init_run_classification = __esm({
6119
6830
  "../shared/dist/run-classification.js"() {
6120
6831
  "use strict";
6121
6832
  USER_CANCELLED_RUN_ERROR = "Cancelled by user";
6833
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR = "Cancelled by platform support";
6834
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR = "Cancelled by platform admin";
6835
+ ADMIN_FORCE_CANCELLED_RUN_ERROR = "Admin force-cancelled all active runs";
6836
+ CHAIN_CANCELLED_RUN_ERROR = "Cancelled: parent run cancelled";
6837
+ RETRY_HELD_RUN_ERROR = "Cancelled: auto generation paused";
6838
+ CANCELLED_REASON_PREFIXES = [
6839
+ USER_CANCELLED_RUN_ERROR,
6840
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR,
6841
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR,
6842
+ ADMIN_FORCE_CANCELLED_RUN_ERROR,
6843
+ CHAIN_CANCELLED_RUN_ERROR,
6844
+ RETRY_HELD_RUN_ERROR
6845
+ ];
6846
+ isUserCancelledError = isCancelledReason;
6122
6847
  HEAL_PATCH_HELD_RUN_ERROR_PREFIX = "Heal patch held";
6123
6848
  RUNNER_INTERRUPT_ERROR_PREFIX = "Runner shutting down";
6124
6849
  SERVER_INTERRUPT_ERROR_PREFIX = "Run interrupted by server restart";
@@ -6134,6 +6859,7 @@ var init_run_classification = __esm({
6134
6859
  UNEXPECTED_PASS_ERROR_PREFIX = "UNEXPECTED_PASS:";
6135
6860
  BUG_TEST_BROKEN_ERROR_PREFIX = "BUG_TEST_BROKEN:";
6136
6861
  BUG_TEST_BROKEN_REASON = "bug test broken (not a bug verdict)";
6862
+ TEST_DID_NOT_RUN_ERROR = "Test did not run (a dependency failed or the run was interrupted)";
6137
6863
  EMAIL_NOT_RECEIVED_PREFIXES = [
6138
6864
  "VERIFICATION_EMAIL_NOT_RECEIVED:",
6139
6865
  "VERIFICATION_EMAIL_HAS_NO_CODE:",
@@ -6146,9 +6872,10 @@ var init_run_classification = __esm({
6146
6872
  ];
6147
6873
  ENVIRONMENTAL_ERROR_STORED_PREFIXES = [
6148
6874
  ...ENVIRONMENTAL_ERROR_PREFIXES.flatMap((prefix) => [prefix, `Error: ${prefix}`]),
6149
- // Written by the cancel routes as is, with no prefix wrapper.
6150
- USER_CANCELLED_RUN_ERROR
6875
+ // Written by the cancels as is, with no prefix wrapper.
6876
+ ...CANCELLED_REASON_PREFIXES
6151
6877
  ];
6878
+ FEATURE_ROW_RUN_WINDOW = 10;
6152
6879
  BUG_SURFACE_SUPPRESSING_TAGS = ["@rate-limit-blocked", "@quota-exceeded", "@auth-lost"];
6153
6880
  QUARANTINE_CLOSE_TAGS = [
6154
6881
  "@bug-found",
@@ -8581,180 +9308,6 @@ var init_admin_view_return_to = __esm({
8581
9308
  }
8582
9309
  });
8583
9310
 
8584
- // ../shared/dist/auth-plan.js
8585
- function hasInlineLoginSteps(playwrightCode) {
8586
- if (!playwrightCode)
8587
- return false;
8588
- return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
8589
- }
8590
- function hasGeneratedAuthGuard(playwrightCode) {
8591
- if (!playwrightCode)
8592
- return false;
8593
- return /\/\/ >>> VALIDATE_QA_AUTH v(\d+)\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\1\b/.test(playwrightCode);
8594
- }
8595
- function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
8596
- if (!sharedAuthInjectEnabled)
8597
- return false;
8598
- if (tags.includes("@auth"))
8599
- return false;
8600
- if (tags.includes("@api"))
8601
- return true;
8602
- if (!tags.includes("@needs-auth"))
8603
- return false;
8604
- if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
8605
- return true;
8606
- const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
8607
- const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
8608
- return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
8609
- }
8610
- function codeReadsStoredCredentials(code) {
8611
- if (!code)
8612
- return false;
8613
- if (/__vqaLogin\b/.test(code))
8614
- return true;
8615
- for (const m of code.matchAll(/TEST_[A-Z0-9_]+/g)) {
8616
- if (/_(?:EMAIL|PASSWORD)/.test(m[0]))
8617
- return true;
8618
- }
8619
- for (const m of code.matchAll(/process\.env\s*\[([^\]]*)\]?/g)) {
8620
- if (!/^\s*(['"])[^'"`]*\1\s*$/.test(m[1]))
8621
- return true;
8622
- }
8623
- return false;
8624
- }
8625
- function explicitAuthTypeTags(t) {
8626
- const found = /* @__PURE__ */ new Set();
8627
- for (const tag of t) {
8628
- if (AUTH_TYPE_TAGS.includes(tag))
8629
- found.add(tag);
8630
- else if (Object.hasOwn(AUTH_TAG_ALIASES, tag))
8631
- found.add(AUTH_TAG_ALIASES[tag]);
8632
- }
8633
- return [...found];
8634
- }
8635
- function invalidPlan(configurationError) {
8636
- return {
8637
- version: 1,
8638
- source: "invalid",
8639
- acquisition: "unsupported",
8640
- presentation: "none",
8641
- needsCookieState: false,
8642
- needsStandingHeaders: false,
8643
- standingHeadersMode: "none",
8644
- needsLoginCreds: false,
8645
- requiredEnvKeys: [],
8646
- configurationError
8647
- };
8648
- }
8649
- function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
8650
- return {
8651
- version: 1,
8652
- source: "explicit",
8653
- authTypeTag: authTypeTag2,
8654
- acquisition,
8655
- presentation,
8656
- needsCookieState: opts.cookie,
8657
- needsStandingHeaders: opts.standingMode === "required",
8658
- standingHeadersMode: opts.standingMode,
8659
- needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
8660
- requiredEnvKeys: opts.requiredEnvKeys
8661
- };
8662
- }
8663
- function deriveAuthPlan(tags, code, opts) {
8664
- const t = Array.isArray(tags) ? tags : [];
8665
- const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
8666
- const isApi = t.includes("@api");
8667
- const explicit = isApi ? explicitAuthTypeTags(t) : [];
8668
- if (explicit.length > 0) {
8669
- if (t.includes("@auth")) {
8670
- return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
8671
- }
8672
- const loginCompose = explicit.filter((e) => e === AUTH_TAG_COOKIE_STATE || e === AUTH_TAG_LOGIN_COOKIE || e === AUTH_TAG_LOGIN_BEARER);
8673
- if (explicit.length === 2 && explicit.includes(AUTH_TAG_STANDING) && loginCompose.length === 1) {
8674
- const composedKeys = [...TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY];
8675
- switch (loginCompose[0]) {
8676
- case AUTH_TAG_COOKIE_STATE:
8677
- return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "required", requiredEnvKeys: composedKeys });
8678
- case AUTH_TAG_LOGIN_COOKIE:
8679
- return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
8680
- case AUTH_TAG_LOGIN_BEARER:
8681
- return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
8682
- }
8683
- }
8684
- if (explicit.length > 1) {
8685
- return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
8686
- }
8687
- switch (explicit[0]) {
8688
- case AUTH_TAG_UNSUPPORTED:
8689
- return {
8690
- ...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
8691
- };
8692
- case AUTH_TAG_NONE:
8693
- return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
8694
- case AUTH_TAG_STANDING:
8695
- return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
8696
- case AUTH_TAG_LOGIN_BEARER:
8697
- return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
8698
- case AUTH_TAG_LOGIN_COOKIE:
8699
- return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
8700
- case AUTH_TAG_COOKIE_STATE:
8701
- return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
8702
- }
8703
- }
8704
- const cookie = legacyNeedsCookieState(t, code, sharedFlag);
8705
- const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
8706
- const selfLoginWithoutStoredCreds = t.includes("@auth") && !t.includes("@needs-auth") && !isApi && !!code?.trim() && !/TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin\b/.test(code);
8707
- const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
8708
- const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
8709
- return {
8710
- version: 1,
8711
- source: "legacy",
8712
- acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
8713
- presentation: authedLegacy ? "cookie" : "none",
8714
- needsCookieState: cookie,
8715
- needsStandingHeaders: false,
8716
- // A legacy bare-@api test keeps injecting standing headers IF the project configured them
8717
- // (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
8718
- standingHeadersMode: isApi ? "legacy-if-configured" : "none",
8719
- needsLoginCreds,
8720
- requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
8721
- };
8722
- }
8723
- function authTypeTag(tags) {
8724
- const t = Array.isArray(tags) ? tags : [];
8725
- const explicit = explicitAuthTypeTags(t);
8726
- return explicit.length === 1 ? explicit[0] : void 0;
8727
- }
8728
- 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;
8729
- var init_auth_plan = __esm({
8730
- "../shared/dist/auth-plan.js"() {
8731
- "use strict";
8732
- AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
8733
- AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
8734
- AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
8735
- AUTH_TAG_STANDING = "@auth-standing";
8736
- AUTH_TAG_NONE = "@auth-none";
8737
- AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
8738
- AUTH_TYPE_TAGS = [
8739
- AUTH_TAG_COOKIE_STATE,
8740
- AUTH_TAG_LOGIN_COOKIE,
8741
- AUTH_TAG_LOGIN_BEARER,
8742
- AUTH_TAG_STANDING,
8743
- AUTH_TAG_NONE,
8744
- AUTH_TAG_UNSUPPORTED
8745
- ];
8746
- AUTH_TAG_ALIASES = {
8747
- "@auth-cookie": AUTH_TAG_COOKIE_STATE,
8748
- "@auth-bearer": AUTH_TAG_LOGIN_BEARER,
8749
- "@auth-token": AUTH_TAG_LOGIN_BEARER,
8750
- "@auth-api-key": AUTH_TAG_STANDING,
8751
- "@auth-standing-header": AUTH_TAG_STANDING
8752
- };
8753
- TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
8754
- API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
8755
- }
8756
- });
8757
-
8758
9311
  // ../shared/dist/auth-block-detector.js
8759
9312
  function stripEchoLines(text) {
8760
9313
  return text.split("\n").filter((line) => !ECHO_LINE_RE.test(line)).join("\n");
@@ -8762,13 +9315,16 @@ function stripEchoLines(text) {
8762
9315
  function matchesAny(text, patterns) {
8763
9316
  return patterns.some((p) => p.test(text));
8764
9317
  }
9318
+ function isIntentionalNegativeAuthTest(input) {
9319
+ const meta = [input.testName, input.description].filter(Boolean).join(" ");
9320
+ return matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH);
9321
+ }
8765
9322
  function detectAuthBlock(input) {
8766
9323
  const tags = input.tags ?? [];
8767
9324
  const isAuthed = tags.some((t) => AUTHED_TAGS.includes(t));
8768
9325
  if (!isAuthed)
8769
9326
  return NO_DETECTION;
8770
- const meta = [input.testName, input.description].filter(Boolean).join(" ");
8771
- if (matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH))
9327
+ if (isIntentionalNegativeAuthTest(input))
8772
9328
  return NO_DETECTION;
8773
9329
  const sources = [];
8774
9330
  if (input.error)
@@ -8871,7 +9427,24 @@ var init_auth_block_detector = __esm({
8871
9427
  /\b(reject|deny|denied|block(ed)?)\b[^\n]{0,20}\b(login|sign[\s-]?in|auth|credential|password)\b/i
8872
9428
  ];
8873
9429
  ECHO_LINE_RE = /^\s*(?:(?:📋|🎭|🌐|⚙️?)|(?:stderr:\s*|stdout:\s*)?Locator:)/u;
8874
- 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;
9430
+ 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;
9431
+ }
9432
+ });
9433
+
9434
+ // ../shared/dist/auth-session-stamp.js
9435
+ function parseAuthSessionStamp(value) {
9436
+ if (!value || typeof value !== "object" || Array.isArray(value))
9437
+ return null;
9438
+ const { shared, role, generation } = value;
9439
+ if (shared !== true || role !== "producer" && role !== "follower")
9440
+ return null;
9441
+ return typeof generation === "string" && AUTH_SESSION_GENERATION_RE.test(generation) ? { shared: true, role, generation } : { shared: true, role };
9442
+ }
9443
+ var AUTH_SESSION_GENERATION_RE;
9444
+ var init_auth_session_stamp = __esm({
9445
+ "../shared/dist/auth-session-stamp.js"() {
9446
+ "use strict";
9447
+ AUTH_SESSION_GENERATION_RE = /^[0-9a-z]{1,16}$/;
8875
9448
  }
8876
9449
  });
8877
9450
 
@@ -9795,12 +10368,70 @@ function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
9795
10368
  function isAuthReadinessGuardEnabled(value = process.env.AUTH_READINESS_GUARD) {
9796
10369
  return value?.trim().toLowerCase() !== "false";
9797
10370
  }
10371
+ function visualBaselinesEnabled(projectId, raw = process.env.VISUAL_BASELINES_PROJECTS) {
10372
+ if (typeof projectId !== "string" || projectId.trim() === "")
10373
+ return false;
10374
+ const listed = (raw ?? "").split(",").map((entry) => entry.trim()).filter(Boolean);
10375
+ return listed.includes("*") || listed.includes(projectId);
10376
+ }
9798
10377
  var init_feature_flags = __esm({
9799
10378
  "../shared/dist/feature-flags.js"() {
9800
10379
  "use strict";
9801
10380
  }
9802
10381
  });
9803
10382
 
10383
+ // ../shared/dist/visual-compare-limits.js
10384
+ function isSafeMaskSelector(selector) {
10385
+ if (typeof selector !== "string")
10386
+ return false;
10387
+ if (selector.trim().length === 0)
10388
+ return false;
10389
+ if (selector.length > VISUAL_MASK_SELECTOR_MAX_LENGTH)
10390
+ return false;
10391
+ return !VISUAL_MASK_SELECTOR_FORBIDDEN.test(selector);
10392
+ }
10393
+ var VISUAL_MASK_SELECTORS_MAX, VISUAL_MASK_SELECTOR_MAX_LENGTH, VISUAL_MASK_SELECTOR_FORBIDDEN;
10394
+ var init_visual_compare_limits = __esm({
10395
+ "../shared/dist/visual-compare-limits.js"() {
10396
+ "use strict";
10397
+ VISUAL_MASK_SELECTORS_MAX = 50;
10398
+ VISUAL_MASK_SELECTOR_MAX_LENGTH = 300;
10399
+ VISUAL_MASK_SELECTOR_FORBIDDEN = /[{};@<\r\n]/;
10400
+ }
10401
+ });
10402
+
10403
+ // ../shared/dist/visual-baseline-identity.js
10404
+ function isBrowserVersion(value) {
10405
+ return typeof value === "string" && value.length <= 32 && BROWSER_VERSION.test(value);
10406
+ }
10407
+ function renderIdentity(flavour, version, image) {
10408
+ if (!/^[a-z][a-z-]{0,31}$/.test(flavour) || !/^[a-z][a-z-]{0,31}$/.test(image) || !isBrowserVersion(version))
10409
+ return null;
10410
+ const identity = `${flavour}/${version}/${image}`;
10411
+ return identity.length <= RENDER_IDENTITY_MAX_LENGTH ? identity : null;
10412
+ }
10413
+ function v1RenderIdentity(version) {
10414
+ return renderIdentity(VISUAL_V1_IDENTITY.flavour, version, VISUAL_V1_IDENTITY.image);
10415
+ }
10416
+ function parseRenderIdentity(value) {
10417
+ if (typeof value !== "string" || value.length > RENDER_IDENTITY_MAX_LENGTH)
10418
+ return null;
10419
+ const parts = value.split("/");
10420
+ if (parts.length !== 3)
10421
+ return null;
10422
+ const [flavour, version, image] = parts;
10423
+ return renderIdentity(flavour, version, image) === value ? { flavour, version, image } : null;
10424
+ }
10425
+ var VISUAL_V1_IDENTITY, RENDER_IDENTITY_MAX_LENGTH, BROWSER_VERSION;
10426
+ var init_visual_baseline_identity = __esm({
10427
+ "../shared/dist/visual-baseline-identity.js"() {
10428
+ "use strict";
10429
+ VISUAL_V1_IDENTITY = { platform: "linux", image: "runner-fast", flavour: "headless-shell" };
10430
+ RENDER_IDENTITY_MAX_LENGTH = 64;
10431
+ BROWSER_VERSION = /^\d{1,4}(\.\d{1,6}){0,3}$/;
10432
+ }
10433
+ });
10434
+
9804
10435
  // ../shared/dist/analytics-url.js
9805
10436
  function isSensitiveQueryKey(key) {
9806
10437
  return SENSITIVE_QUERY_KEY_RE.test(key);
@@ -10103,7 +10734,7 @@ var init_agentic_cli_runtime = __esm({
10103
10734
  GROK_CLI_DEFAULT_MODEL = "grok-4.7";
10104
10735
  GROK_CLI_DEFAULT_REASONING_EFFORT = "high";
10105
10736
  CLAUDE_CLI_DEFAULT_MODEL = "claude-sonnet-5-5";
10106
- CLAUDE_CLI_DEFAULT_EFFORT = "max";
10737
+ CLAUDE_CLI_DEFAULT_EFFORT = "high";
10107
10738
  MUSE_CLI_DEFAULT_MODEL = "muse-spark-1.3-contributor";
10108
10739
  MUSE_CLI_DEFAULT_EFFORT = "high";
10109
10740
  CODEX_CLI_DEFAULT_MODEL = "gpt-6.1-sol";
@@ -10158,6 +10789,17 @@ function openCodeGoWireModel(id) {
10158
10789
  const trimmed = id?.trim() ?? "";
10159
10790
  return isOpenCodeGoModelId(trimmed) ? trimmed.slice(OPENCODE_GO_PREFIX.length) : null;
10160
10791
  }
10792
+ function isZaiModelId(id) {
10793
+ const trimmed = id?.trim() ?? "";
10794
+ return ZAI_MODEL_PATTERN.test(trimmed) && !trimmed.includes("..");
10795
+ }
10796
+ function hasZaiPrefix(id) {
10797
+ return (id?.trim() ?? "").startsWith(ZAI_PREFIX);
10798
+ }
10799
+ function zaiWireModel(id) {
10800
+ const trimmed = id?.trim() ?? "";
10801
+ return isZaiModelId(trimmed) ? trimmed.slice(ZAI_PREFIX.length) : null;
10802
+ }
10161
10803
  function isEngineId(value) {
10162
10804
  return CLI_ENGINE_MODEL_IDS.includes(value);
10163
10805
  }
@@ -10254,7 +10896,7 @@ function museCliContributorMode(env = process.env) {
10254
10896
  const raw = (env.MUSE_CLI_CONTRIBUTOR_MODE ?? "").trim().toLowerCase();
10255
10897
  return raw === "off" || raw === "false" || raw === "0" || raw === "no" ? "off" : "on";
10256
10898
  }
10257
- 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;
10899
+ 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;
10258
10900
  var init_engine_spec = __esm({
10259
10901
  "../shared/dist/engine-spec.js"() {
10260
10902
  "use strict";
@@ -10314,6 +10956,8 @@ var init_engine_spec = __esm({
10314
10956
  OPENROUTER_MODEL_PATTERN = /^openrouter:[a-z0-9][a-z0-9._-]{0,40}\/[A-Za-z0-9._:-]{1,100}$/;
10315
10957
  OPENCODE_GO_PREFIX = "opencode-go:";
10316
10958
  OPENCODE_GO_MODEL_PATTERN = /^opencode-go:[a-z0-9][a-z0-9._-]{0,100}$/;
10959
+ ZAI_PREFIX = "zai:";
10960
+ ZAI_MODEL_PATTERN = /^zai:glm-[a-z0-9][a-z0-9._-]{0,60}$/;
10317
10961
  }
10318
10962
  });
10319
10963
 
@@ -10598,6 +11242,8 @@ var init_api_model_catalog = __esm({
10598
11242
  authCaptureModel: "Login capture",
10599
11243
  plannerModel: "Scenario Planner",
10600
11244
  plannerGateModel: "Planner Gate",
11245
+ pwAgentsHealerModel: "Test Healer (Playwright agents)",
11246
+ pwAgentsReviewModel: "Plan Review (Playwright agents)",
10601
11247
  generationModel: "Code Generation",
10602
11248
  reviewModel: "Code Review",
10603
11249
  apiTestModel: "API Test Generator",
@@ -10622,11 +11268,6 @@ var init_api_model_catalog = __esm({
10622
11268
  });
10623
11269
 
10624
11270
  // ../shared/dist/provider-chains.js
10625
- function isClaudeModel(id) {
10626
- if (/claude|anthropic/i.test(id))
10627
- return true;
10628
- return API_MODEL_CATALOG.some((m) => m.provider === "anthropic" && (m.id === id || m.apiModel === id));
10629
- }
10630
11271
  function chainKey(modelId) {
10631
11272
  const id = canonicalModelId(modelId);
10632
11273
  if (!id)
@@ -10634,35 +11275,334 @@ function chainKey(modelId) {
10634
11275
  const entry = API_MODEL_CATALOG.find((m) => m.id === id || m.apiModel === id);
10635
11276
  return entry?.id ?? id;
10636
11277
  }
11278
+ function has(map, key) {
11279
+ return Object.prototype.hasOwnProperty.call(map, key);
11280
+ }
10637
11281
  function providerChainFor(modelId) {
10638
11282
  const key = chainKey(modelId);
10639
- if (!key || isClaudeModel(key))
11283
+ if (key && has(_overrideHops, key))
11284
+ return _overrideHops[key];
11285
+ return key && has(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
11286
+ }
11287
+ function providerChainLeadsFor(modelId) {
11288
+ const key = chainKey(modelId);
11289
+ if (key && has(_overrideLeads, key))
11290
+ return _overrideLeads[key];
11291
+ return key && has(PROVIDER_CHAIN_LEADS, key) ? PROVIDER_CHAIN_LEADS[key] : [];
11292
+ }
11293
+ function allChains() {
11294
+ const ordered = (map, override) => [...Object.entries(map).filter(([key]) => !has(override, key)), ...Object.entries(override)];
11295
+ return [...ordered(PROVIDER_CHAINS, _overrideHops), ...ordered(PROVIDER_CHAIN_LEADS, _overrideLeads)];
11296
+ }
11297
+ function isProviderChainHopId(modelId) {
11298
+ const id = modelId ?? "";
11299
+ return id !== "" && allChains().some(([, hops]) => hops.includes(id));
11300
+ }
11301
+ function providerChainPrimariesForHop(hopId) {
11302
+ const id = hopId ?? "";
11303
+ if (!id)
11304
+ return [];
11305
+ return allChains().filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
11306
+ }
11307
+ function providerChainHostOf(value, modelValue) {
11308
+ if (value === modelValue)
11309
+ return "own";
11310
+ if (hasZaiPrefix(value))
11311
+ return "zai";
11312
+ if (hasOpenCodeGoPrefix(value))
11313
+ return "opencode-go";
11314
+ if (hasOpenRouterPrefix(value))
11315
+ return "openrouter";
11316
+ return "own";
11317
+ }
11318
+ function providerChainHostProvider(value) {
11319
+ if (typeof value !== "string" || !value)
11320
+ return null;
11321
+ if (hasOpenRouterPrefix(value))
11322
+ return "openrouter";
11323
+ if (hasOpenCodeGoPrefix(value))
11324
+ return "opencode-go";
11325
+ if (hasZaiPrefix(value))
11326
+ return "zai";
11327
+ const key = chainKey(value);
11328
+ return API_MODEL_CATALOG.find((m) => m.id === key)?.provider ?? GEMINI_ASSISTANT_MODELS.find((m) => m.id === key)?.provider ?? null;
11329
+ }
11330
+ function providerHostPlan(modelId) {
11331
+ const key = chainKey(modelId);
11332
+ if (!key)
11333
+ return [];
11334
+ const own = { host: hasOpenRouterPrefix(key) ? "openrouter" : "own", value: key };
11335
+ const steps = [
11336
+ ...providerChainLeadsFor(key).map((value) => ({ host: providerChainHostOf(value, key), value })),
11337
+ own,
11338
+ ...providerChainFor(key).map((value) => ({ host: providerChainHostOf(value, key), value }))
11339
+ ];
11340
+ const notOffered = Object.prototype.hasOwnProperty.call(PROVIDER_HOST_NOT_OFFERED, key) ? PROVIDER_HOST_NOT_OFFERED[key] : {};
11341
+ for (const [host, reason] of Object.entries(notOffered)) {
11342
+ const at = steps.findIndex((step) => step === own) + 1;
11343
+ steps.splice(at, 0, { host, value: null, notOffered: reason });
11344
+ }
11345
+ return steps;
11346
+ }
11347
+ function providerChainModelKey(modelId) {
11348
+ return chainKey(modelId);
11349
+ }
11350
+ function hasProviderChainLeadsByDefault(modelId) {
11351
+ const key = chainKey(modelId);
11352
+ return key !== null && has(PROVIDER_CHAIN_LEADS, key);
11353
+ }
11354
+ function isChainablePrimary(id) {
11355
+ return typeof id === "string" && id !== "" && (has(PROVIDER_CHAINS, id) || has(PROVIDER_CHAIN_LEADS, id));
11356
+ }
11357
+ function defaultProviderChainEntries(primary) {
11358
+ if (!isChainablePrimary(primary))
10640
11359
  return [];
10641
- return Object.prototype.hasOwnProperty.call(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
11360
+ const leads = has(PROVIDER_CHAIN_LEADS, primary) ? PROVIDER_CHAIN_LEADS[primary] : [];
11361
+ const hops = has(PROVIDER_CHAINS, primary) ? PROVIDER_CHAINS[primary] : [];
11362
+ return [...leads, primary, ...hops].map((hop) => ({ hop, enabled: true }));
11363
+ }
11364
+ function allowedProviderChainHosts(primary) {
11365
+ return defaultProviderChainEntries(primary).map((entry) => entry.hop);
11366
+ }
11367
+ function isPlainObject2(value) {
11368
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
11369
+ }
11370
+ function validateProviderChainEntries(primary, entries) {
11371
+ if (!isChainablePrimary(primary))
11372
+ return `${String(primary).slice(0, 80)} has no provider chain to order`;
11373
+ if (!Array.isArray(entries))
11374
+ return `${primary}: the chain must be a list`;
11375
+ if (entries.length > PROVIDER_CHAIN_MAX_ENTRIES)
11376
+ return `${primary}: at most ${PROVIDER_CHAIN_MAX_ENTRIES} hosts`;
11377
+ const allowed = allowedProviderChainHosts(primary);
11378
+ const seen = /* @__PURE__ */ new Set();
11379
+ for (const entry of entries) {
11380
+ if (!isPlainObject2(entry))
11381
+ return `${primary}: each host must be { hop, enabled }`;
11382
+ const keys = Object.keys(entry);
11383
+ if (keys.length !== 2 || !keys.includes("hop") || !keys.includes("enabled"))
11384
+ return `${primary}: each host must be { hop, enabled } and nothing else`;
11385
+ const { hop, enabled } = entry;
11386
+ if (typeof enabled !== "boolean")
11387
+ return `${primary}: enabled must be true or false`;
11388
+ if (typeof hop !== "string" || !hop)
11389
+ return `${primary}: hop must be a model value`;
11390
+ if (!allowed.includes(hop))
11391
+ return `${primary}: ${hop.slice(0, 80)} is not a host of this model`;
11392
+ if (seen.has(hop))
11393
+ return `${primary}: ${hop} is listed twice`;
11394
+ if (hop === primary && !enabled)
11395
+ return `${primary}: the model's own value stays on (pick another model on AI Models instead)`;
11396
+ seen.add(hop);
11397
+ }
11398
+ if (!seen.has(primary))
11399
+ return `${primary}: list the model's own value once, where its own host sits`;
11400
+ return null;
11401
+ }
11402
+ function decodeProviderChains(raw) {
11403
+ let value = raw;
11404
+ if (typeof raw === "string") {
11405
+ if (raw.length > PROVIDER_CHAINS_SETTING_MAX_CHARS)
11406
+ return { ok: false, error: `AI_PROVIDER_CHAINS is too long (at most ${PROVIDER_CHAINS_SETTING_MAX_CHARS} characters)` };
11407
+ try {
11408
+ value = JSON.parse(raw);
11409
+ } catch {
11410
+ return { ok: false, error: "AI_PROVIDER_CHAINS must be valid JSON" };
11411
+ }
11412
+ } else if (JSON.stringify(raw ?? null).length > PROVIDER_CHAINS_SETTING_MAX_CHARS) {
11413
+ return { ok: false, error: `AI_PROVIDER_CHAINS is too long (at most ${PROVIDER_CHAINS_SETTING_MAX_CHARS} characters)` };
11414
+ }
11415
+ if (!isPlainObject2(value))
11416
+ return { ok: false, error: 'AI_PROVIDER_CHAINS must be an object: { "<model>": [ { "hop": "<model value>", "enabled": true } ] }' };
11417
+ if (Object.keys(value).length > PROVIDER_CHAIN_MAX_PRIMARIES)
11418
+ return { ok: false, error: `AI_PROVIDER_CHAINS names at most ${PROVIDER_CHAIN_MAX_PRIMARIES} models` };
11419
+ return { ok: true, value };
11420
+ }
11421
+ function checkedProviderChains(value) {
11422
+ const out = {};
11423
+ for (const [primary, entries] of Object.entries(value)) {
11424
+ const problem = validateProviderChainEntries(primary, entries);
11425
+ if (problem)
11426
+ return { ok: false, error: problem };
11427
+ out[primary] = entries.map(({ hop, enabled }) => ({ hop, enabled }));
11428
+ }
11429
+ return { ok: true, value: out };
11430
+ }
11431
+ function parseProviderChainOverrides(raw) {
11432
+ const decoded = decodeProviderChains(raw);
11433
+ return decoded.ok ? checkedProviderChains(decoded.value) : decoded;
11434
+ }
11435
+ function parseStoredProviderChainOverrides(raw) {
11436
+ const decoded = decodeProviderChains(raw);
11437
+ if (!decoded.ok)
11438
+ return decoded;
11439
+ const dropped = [];
11440
+ const kept = {};
11441
+ for (const [primary, entries] of Object.entries(decoded.value)) {
11442
+ if (!isChainablePrimary(primary) || !Array.isArray(entries)) {
11443
+ kept[primary] = entries;
11444
+ continue;
11445
+ }
11446
+ const allowed = allowedProviderChainHosts(primary);
11447
+ kept[primary] = entries.filter((entry) => {
11448
+ const hop = isPlainObject2(entry) ? entry.hop : void 0;
11449
+ if (typeof hop !== "string" || hop === "" || allowed.includes(hop))
11450
+ return true;
11451
+ dropped.push({ primary, hop: hop.slice(0, 80) });
11452
+ return false;
11453
+ });
11454
+ }
11455
+ const checked = checkedProviderChains(kept);
11456
+ return checked.ok ? { ...checked, dropped } : checked;
11457
+ }
11458
+ function validateProviderChainsSetting(raw) {
11459
+ const parsed = parseProviderChainOverrides(raw);
11460
+ return parsed.ok ? null : parsed.error;
10642
11461
  }
10643
- function isProviderChainHopId(modelId) {
10644
- const id = modelId ?? "";
10645
- return id !== "" && Object.values(PROVIDER_CHAINS).some((hops) => hops.includes(id));
11462
+ function setProviderChainOverrides(value, onDropped) {
11463
+ const parsed = value === null || value === void 0 || value === "" ? null : parseStoredProviderChainOverrides(value);
11464
+ if (parsed?.ok && parsed.dropped.length > 0)
11465
+ onDropped?.(parsed.dropped);
11466
+ _overrides = parsed?.ok ? parsed.value : null;
11467
+ _overrideLeads = {};
11468
+ _overrideHops = {};
11469
+ for (const [primary, entries] of Object.entries(_overrides ?? {})) {
11470
+ const at = entries.findIndex((entry) => entry.hop === primary);
11471
+ const on = (list) => list.filter((entry) => entry.enabled).map((entry) => entry.hop);
11472
+ _overrideLeads[primary] = on(entries.slice(0, at));
11473
+ _overrideHops[primary] = on(entries.slice(at + 1));
11474
+ }
11475
+ return parsed === null || parsed.ok;
10646
11476
  }
10647
- function providerChainPrimariesForHop(hopId) {
10648
- const id = hopId ?? "";
10649
- if (!id)
10650
- return [];
10651
- return Object.entries(PROVIDER_CHAINS).filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
11477
+ function getProviderChainOverrides() {
11478
+ return _overrides;
10652
11479
  }
10653
- var PROVIDER_CHAINS;
11480
+ 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;
10654
11481
  var init_provider_chains = __esm({
10655
11482
  "../shared/dist/provider-chains.js"() {
10656
11483
  "use strict";
10657
11484
  init_api_model_catalog();
11485
+ init_engine_spec();
10658
11486
  PROVIDER_CHAINS = {
10659
- "minimax-m3": ["openrouter:minimax/minimax-m3", "opencode-go:minimax-m3"],
10660
- "minimax-m3.1-flash": ["openrouter:minimax/minimax-m3.1-flash", "opencode-go:minimax-m3.1-flash"],
11487
+ "minimax-m3": ["opencode-go:minimax-m3", "openrouter:minimax/minimax-m3"],
11488
+ "minimax-m3.1-flash": ["opencode-go:minimax-m3.1-flash", "openrouter:minimax/minimax-m3.1-flash"],
10661
11489
  "muse-spark-1.3-contributor": ["openrouter:meta/muse-spark-1.3-contributor"],
10662
- "openrouter:z-ai/glm-5.3-flash": ["opencode-go:glm-5.3-flash"],
10663
- "openrouter:z-ai/glm-5.3": ["opencode-go:glm-5.3"],
10664
- "openrouter:z-ai/glm-5.2": ["opencode-go:glm-5.2"]
11490
+ "claude-haiku-5-5": ["opencode-go:claude-haiku-5-5", "openrouter:anthropic/claude-haiku-5.5"],
11491
+ "gemini-3.1-pro-preview": ["openrouter:google/gemini-3.1-pro-preview"],
11492
+ "deepseek-flash": ["opencode-go:deepseek-v4.1-flash", "openrouter:deepseek/deepseek-v4.1-flash"],
11493
+ "deepseek-v4-pro": ["opencode-go:deepseek-v4-pro", "openrouter:deepseek/deepseek-v4-pro"]
10665
11494
  };
11495
+ PROVIDER_CHAIN_LEADS = {
11496
+ "openrouter:z-ai/glm-5.3-flash": ["zai:glm-5.3-flash", "opencode-go:glm-5.3-flash"],
11497
+ "openrouter:z-ai/glm-5.3": ["zai:glm-5.3", "opencode-go:glm-5.3"],
11498
+ "openrouter:z-ai/glm-5.2": ["zai:glm-5.2", "opencode-go:glm-5.2"]
11499
+ };
11500
+ PROVIDER_HOST_NOT_OFFERED = {
11501
+ "muse-spark-1.3-contributor": { "opencode-go": "OpenCode Go serves Muse only on its Responses API" },
11502
+ "gemini-3.1-pro-preview": { "opencode-go": "OpenCode Go does not serve Gemini" }
11503
+ };
11504
+ PROVIDER_CHAIN_MAX_ENTRIES = 8;
11505
+ PROVIDER_CHAINS_SETTING_MAX_CHARS = 16e3;
11506
+ PROVIDER_CHAIN_MAX_PRIMARIES = 32;
11507
+ ALLOWED_PROVIDER_CHAIN_HOPS = /* @__PURE__ */ new Set([
11508
+ ...Object.values(PROVIDER_CHAINS).flat(),
11509
+ ...Object.values(PROVIDER_CHAIN_LEADS).flat()
11510
+ ]);
11511
+ _overrides = null;
11512
+ _overrideLeads = {};
11513
+ _overrideHops = {};
11514
+ }
11515
+ });
11516
+
11517
+ // ../shared/dist/provider-accounts.js
11518
+ function modelNames(modelId) {
11519
+ const value = modelId.trim();
11520
+ const names = /* @__PURE__ */ new Set([value]);
11521
+ for (const prefix of HOST_PREFIXES) {
11522
+ if (!value.startsWith(prefix))
11523
+ continue;
11524
+ const bare = value.slice(prefix.length);
11525
+ names.add(bare);
11526
+ const slash = bare.lastIndexOf("/");
11527
+ if (slash >= 0)
11528
+ names.add(bare.slice(slash + 1));
11529
+ }
11530
+ const canonical = canonicalModelId(value);
11531
+ if (canonical) {
11532
+ names.add(canonical);
11533
+ const entry = API_MODEL_CATALOG.find((m) => m.id === canonical || m.apiModel === canonical);
11534
+ if (entry) {
11535
+ names.add(entry.id);
11536
+ if (entry.apiModel)
11537
+ names.add(entry.apiModel);
11538
+ }
11539
+ }
11540
+ return names;
11541
+ }
11542
+ function accountServesModel(models, modelId) {
11543
+ if (!models || models.length === 0)
11544
+ return true;
11545
+ if (!modelId)
11546
+ return false;
11547
+ const names = modelNames(modelId);
11548
+ return models.some((model) => names.has(model.trim()));
11549
+ }
11550
+ function normalizeProviderAccountModels(models) {
11551
+ if (!Array.isArray(models))
11552
+ return null;
11553
+ const out = [];
11554
+ for (const model of models) {
11555
+ if (typeof model !== "string")
11556
+ return null;
11557
+ const trimmed = model.trim();
11558
+ if (!PROVIDER_ACCOUNT_MODEL_PATTERN.test(trimmed) || trimmed.includes(".."))
11559
+ return null;
11560
+ if (!out.includes(trimmed))
11561
+ out.push(trimmed);
11562
+ }
11563
+ return out.length > MAX_PROVIDER_ACCOUNT_MODELS ? null : out;
11564
+ }
11565
+ function parseProviderAccountLists(value, providers) {
11566
+ if (!value || typeof value !== "object" || Array.isArray(value))
11567
+ return null;
11568
+ const parsed = {};
11569
+ for (const provider of providers) {
11570
+ const list = value[provider];
11571
+ if (!Array.isArray(list))
11572
+ continue;
11573
+ const accounts = [];
11574
+ for (const raw of list) {
11575
+ if (!raw || typeof raw !== "object")
11576
+ continue;
11577
+ const { id, label, key, models } = raw;
11578
+ if (typeof id !== "string" || !id.trim() || typeof key !== "string" || !key.trim())
11579
+ continue;
11580
+ const modelList = Array.isArray(models) ? models.filter((m) => typeof m === "string" && m.trim() !== "").map((m) => m.trim()) : [];
11581
+ accounts.push({ id: id.trim(), label: typeof label === "string" && label.trim() ? label.trim() : null, key: key.trim(), models: modelList });
11582
+ }
11583
+ if (accounts.length > 0)
11584
+ parsed[provider] = accounts;
11585
+ }
11586
+ return parsed;
11587
+ }
11588
+ 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;
11589
+ var init_provider_accounts = __esm({
11590
+ "../shared/dist/provider-accounts.js"() {
11591
+ "use strict";
11592
+ init_api_model_catalog();
11593
+ DEFAULT_PROVIDER_ACCOUNT_ID = "primary";
11594
+ PROVIDER_ACCOUNT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,39}$/;
11595
+ MAX_PROVIDER_ACCOUNTS = 10;
11596
+ MAX_PROVIDER_ACCOUNT_MODELS = 50;
11597
+ PROVIDER_ACCOUNT_MODEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,120}$/;
11598
+ OPENCODE_GO_DEEPSEEK_GLM_MODELS = [
11599
+ "deepseek-v4.1-flash",
11600
+ "deepseek-v4-pro",
11601
+ "glm-5.3-flash",
11602
+ "glm-5.3",
11603
+ "glm-5.2"
11604
+ ];
11605
+ HOST_PREFIXES = ["opencode-go:", "zai:", "openrouter:"];
10666
11606
  }
10667
11607
  });
10668
11608
 
@@ -10781,6 +11721,25 @@ var init_project_engine_overrides = __esm({
10781
11721
  }
10782
11722
  });
10783
11723
 
11724
+ // ../shared/dist/trailing-slashes.js
11725
+ function stripTrailingSlashes(value) {
11726
+ let end = value.length;
11727
+ while (end > 0 && value.charCodeAt(end - 1) === 47)
11728
+ end--;
11729
+ return end === value.length ? value : value.slice(0, end);
11730
+ }
11731
+ function stripTrailingChars(value, isTrailing) {
11732
+ let end = value.length;
11733
+ while (end > 0 && isTrailing.test(value[end - 1]))
11734
+ end--;
11735
+ return end === value.length ? value : value.slice(0, end);
11736
+ }
11737
+ var init_trailing_slashes = __esm({
11738
+ "../shared/dist/trailing-slashes.js"() {
11739
+ "use strict";
11740
+ }
11741
+ });
11742
+
10784
11743
  // ../shared/dist/gemini-visual.js
10785
11744
  function visualProDailyBudget(raw) {
10786
11745
  const parsed = raw && /^\s*\d{1,6}\s*$/.test(raw) ? Number(raw.trim()) : NaN;
@@ -10794,7 +11753,7 @@ function isRetiredGeminiModel(id) {
10794
11753
  }
10795
11754
  function geminiNativeModelsUrl(base = process.env.GEMINI_API_BASE) {
10796
11755
  const raw = base && base.trim() || GEMINI_OPENAI_BASE;
10797
- const native = raw.replace(/\/openai\/?$/, "/").replace(/\/+$/, "");
11756
+ const native = stripTrailingSlashes(raw.replace(/\/openai\/?$/, "/"));
10798
11757
  return `${native}/models?pageSize=1000`;
10799
11758
  }
10800
11759
  function asRecord(value) {
@@ -10914,6 +11873,7 @@ var KNOWN_INPUT_USD_PER_M, GEMINI_OPENAI_BASE, DEFAULT_GEMINI_SCREEN_MODEL, DEFA
10914
11873
  var init_gemini_visual = __esm({
10915
11874
  "../shared/dist/gemini-visual.js"() {
10916
11875
  "use strict";
11876
+ init_trailing_slashes();
10917
11877
  KNOWN_INPUT_USD_PER_M = {
10918
11878
  "gemini-2.5-flash-lite": 0.1,
10919
11879
  "gemini-3.1-flash-lite": 0.25,
@@ -12277,12 +13237,13 @@ function truncateAtWord(text, max) {
12277
13237
  const slice = trimmed.slice(0, room + 1);
12278
13238
  const lastSpace = slice.lastIndexOf(" ");
12279
13239
  const cut = lastSpace > room * 0.5 ? slice.slice(0, lastSpace) : trimmed.slice(0, room);
12280
- return `${cut.replace(/[\s,;:.\-–—]+$/u, "")}\u2026`;
13240
+ return `${stripTrailingChars(cut, TRAILING_PUNCTUATION)}\u2026`;
12281
13241
  }
12282
- 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;
13242
+ 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;
12283
13243
  var init_canvas_support = __esm({
12284
13244
  "../shared/dist/canvas-support.js"() {
12285
13245
  "use strict";
13246
+ init_trailing_slashes();
12286
13247
  CANVAS_UNSUPPORTED_CODE = "canvas_unsupported";
12287
13248
  CANVAS_UNSUPPORTED_TITLE = "Games and canvas apps aren't supported yet";
12288
13249
  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.";
@@ -12407,6 +13368,7 @@ var init_canvas_support = __esm({
12407
13368
  TOUCH_NONE_RE = /touch-action\s{0,8}:\s{0,8}none/;
12408
13369
  CODE_TAGS = /* @__PURE__ */ new Set(["script", "link", "flt-glass-pane", "flutter-view", "flt-scene-host"]);
12409
13370
  STATIC_STANDARD_MIN_INTERACTIVE = 3;
13371
+ TRAILING_PUNCTUATION = /[\s,;:.\-–—]/u;
12410
13372
  }
12411
13373
  });
12412
13374
 
@@ -12589,22 +13551,22 @@ function joinChainedLines(code) {
12589
13551
  const trimmed = lines[i].trimStart();
12590
13552
  if (last !== -1 && trimmed.startsWith(".") && !trimmed.startsWith("...")) {
12591
13553
  if (!lastStripped) {
12592
- lines[last] = stripLineComment2(lines[last]).trimEnd();
13554
+ lines[last] = stripLineComment3(lines[last]).trimEnd();
12593
13555
  lastStripped = true;
12594
13556
  }
12595
13557
  if (lines[last].length <= MAX_LINE_CHARS)
12596
- lines[last] = `${lines[last]} ${stripLineComment2(trimmed).trimEnd()}`;
13558
+ lines[last] = `${lines[last]} ${stripLineComment3(trimmed).trimEnd()}`;
12597
13559
  lines[i] = "";
12598
13560
  continue;
12599
13561
  }
12600
- if (stripLineComment2(lines[i]).trim()) {
13562
+ if (stripLineComment3(lines[i]).trim()) {
12601
13563
  last = i;
12602
13564
  lastStripped = false;
12603
13565
  }
12604
13566
  }
12605
13567
  return lines.join("\n");
12606
13568
  }
12607
- function stripLineComment2(rawLine) {
13569
+ function stripLineComment3(rawLine) {
12608
13570
  if (rawLine.trimStart().startsWith("//"))
12609
13571
  return "";
12610
13572
  const at = rawLine.indexOf(" // ");
@@ -12620,7 +13582,7 @@ function stateLiteralsOf(line) {
12620
13582
  const out = [];
12621
13583
  for (const m of line.matchAll(STRING_LITERAL_RE)) {
12622
13584
  const text = m[1] ?? m[2] ?? m[3] ?? "";
12623
- if (text.includes("placeholder="))
13585
+ if (PLACEHOLDER_ATTR_RE.test(text))
12624
13586
  continue;
12625
13587
  if (PLACEHOLDER_CALL_TAIL_RE.test(line.slice(Math.max(0, m.index - 40), m.index)))
12626
13588
  continue;
@@ -12653,7 +13615,7 @@ function analyzeReadinessPolling(rawCode) {
12653
13615
  offset += rawLine.length + 1;
12654
13616
  if (rawLine.length > MAX_LINE_CHARS)
12655
13617
  continue;
12656
- const line = stripLineComment2(rawLine);
13618
+ const line = stripLineComment3(rawLine);
12657
13619
  if (!line.trim())
12658
13620
  continue;
12659
13621
  const literals = literalsOf(line);
@@ -12716,7 +13678,7 @@ function formatReadinessPollAdvisory(findings) {
12716
13678
  const where = findings.length === 1 ? "One place" : `${findings.length} places`;
12717
13679
  return `${where} re-check a background state once and assert the finished state at once (${short(first.line, 90)} then ${short(first.assertion, 90)}). ${first.reason}`;
12718
13680
  }
12719
- var READINESS_POLL_MAX_CODE_CHARS, MAX_LINE_CHARS, MAX_FINDINGS, ASSERTION_WINDOW_LINES, MAX_BLOCK_CHARS, MAX_HEAD_CHARS, STRING_LITERAL_RE, RECHECK_NAME_RE, RETRY_NAME_RE, PENDING_STATE_RE, LOCATOR_LINE_RE, CLICK_RE, RELOAD_RE, GOTO_RE, ACTION_RE, EXPECT_RE, NEGATED_RE, PENDING_ENDED_RE, POSITIVE_MATCHER_RE, ZERO_COUNT_RE, TEST_END_RE, TEST_START_RE, POLL_OPEN_RE, PLACEHOLDER_CALL_TAIL_RE, POLL_SHAPE;
13681
+ var READINESS_POLL_MAX_CODE_CHARS, MAX_LINE_CHARS, MAX_FINDINGS, ASSERTION_WINDOW_LINES, MAX_BLOCK_CHARS, MAX_HEAD_CHARS, STRING_LITERAL_RE, RECHECK_NAME_RE, RETRY_NAME_RE, PENDING_STATE_RE, LOCATOR_LINE_RE, CLICK_RE, RELOAD_RE, GOTO_RE, ACTION_RE, EXPECT_RE, NEGATED_RE, PENDING_ENDED_RE, POSITIVE_MATCHER_RE, ZERO_COUNT_RE, TEST_END_RE, TEST_START_RE, POLL_OPEN_RE, PLACEHOLDER_ATTR_RE, PLACEHOLDER_CALL_TAIL_RE, POLL_SHAPE;
12720
13682
  var init_readiness_poll = __esm({
12721
13683
  "../shared/dist/readiness-poll.js"() {
12722
13684
  "use strict";
@@ -12729,7 +13691,7 @@ var init_readiness_poll = __esm({
12729
13691
  STRING_LITERAL_RE = /'((?:\\.|[^'\\\n]){0,300})'|"((?:\\.|[^"\\\n]){0,300})"|`((?:\\.|[^`\\\n]){0,300})`/g;
12730
13692
  RECHECK_NAME_RE = /^\s{0,10}(?:check again|check (?:status|availability|now|for updates|progress)|re-?check(?: status)?|update status|see (?:latest|updates))\s{0,10}(?:\.\.\.|…)?\s{0,10}$/i;
12731
13693
  RETRY_NAME_RE = /^\s{0,10}(?:refresh(?: (?:status|list|page|results|now))?|reload(?: (?:page|results|list))?|try again|retry|load (?:again|latest))\s{0,10}(?:\.\.\.|…)?\s{0,10}$/i;
12732
- PENDING_STATE_RE = /\b(?:searching|checking|sending|pending|processing|queued|in progress|loading|generating|calculating|preparing|verifying|syncing|refreshing|not ready|please wait|waiting)\b|\b[a-z]{3,40}(?<!th)ing(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$/i;
13694
+ PENDING_STATE_RE = /\b(?:searching|checking|sending|pending|processing|queued|in progress|loading|generating|calculating|preparing|verifying|syncing|refreshing|not ready|please wait|waiting)\b|\b(?:saving|uploading|downloading|signing|logging|submitting|creating|updating|deleting|importing|exporting|publishing|connecting|redirecting|authenticating|installing|retrieving|fetching|analy[sz]ing|running|rendering|restarting|building|compiling|booting|provisioning|deploying|indexing|migrating|launching|initiali[sz]ing|optimi[sz]ing|converting|processing)(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$|^\s{0,10}(?:starting|scanning|working|applying|finishing)(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$|^\s{0,10}[a-z]{3,40}(?<!th)ing\s{0,3}(?:\.\.\.|…)\s{0,10}$/i;
12733
13695
  LOCATOR_LINE_RE = /\.(?:getBy[A-Z]\w{0,30}|locator|toHaveText|toContainText)\s{0,20}\(/;
12734
13696
  CLICK_RE = /\.(?:click|dblclick|tap)\s{0,20}\(/;
12735
13697
  RELOAD_RE = /\bpage\.reload\s{0,20}\(/;
@@ -12743,6 +13705,7 @@ var init_readiness_poll = __esm({
12743
13705
  TEST_END_RE = /^\s{0,20}\}\s{0,20}\)\s{0,20};?\s{0,20}$/;
12744
13706
  TEST_START_RE = /\btest(?:\.\w{1,20})?\s{0,20}\(/;
12745
13707
  POLL_OPEN_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}async\b|\bexpect\.poll\s{0,20}\(|\bfor\s{0,20}\(|\bwhile\s{0,20}\(|\bdo\s{0,20}\{/g;
13708
+ PLACEHOLDER_ATTR_RE = /placeholder\s{0,4}[*^$~|]?=/;
12746
13709
  PLACEHOLDER_CALL_TAIL_RE = /getByPlaceholder\s{0,20}\(\s{0,20}$/;
12747
13710
  POLL_SHAPE = "await expect(async () => { await page.reload(); await expect(<the finished state>).toBeVisible({ timeout: 1500 }); }).toPass({ timeout: 20000 });";
12748
13711
  }
@@ -12764,7 +13727,7 @@ function blankBlockComments2(code) {
12764
13727
  from = close + 2;
12765
13728
  }
12766
13729
  }
12767
- function stripLineComment3(rawLine) {
13730
+ function stripLineComment4(rawLine) {
12768
13731
  if (rawLine.trimStart().startsWith("//"))
12769
13732
  return "";
12770
13733
  const at = rawLine.indexOf(" // ");
@@ -12775,7 +13738,7 @@ function logicalLines(code) {
12775
13738
  for (const raw of code.split("\n")) {
12776
13739
  if (raw.length > MAX_LINE_CHARS2)
12777
13740
  continue;
12778
- const line = stripLineComment3(raw).trimEnd();
13741
+ const line = stripLineComment4(raw).trimEnd();
12779
13742
  if (!line.trim())
12780
13743
  continue;
12781
13744
  const trimmed = line.trimStart();
@@ -13137,13 +14100,33 @@ function short4(text, max) {
13137
14100
  const flat = text.replace(/\s+/g, " ").trim();
13138
14101
  return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
13139
14102
  }
14103
+ function stripFractionZeros(s) {
14104
+ let end = s.length;
14105
+ while (end > 0 && s[end - 1] === "0")
14106
+ end--;
14107
+ if (end === s.length)
14108
+ return s;
14109
+ const dot = s.lastIndexOf(".");
14110
+ if (dot < 0)
14111
+ return s;
14112
+ return s.slice(0, end === dot + 1 ? dot : end);
14113
+ }
13140
14114
  function digitsOf(token) {
13141
- return token.replace(/[^\d.]/g, "").replace(/\.0+$|(\.\d*?)0+$/, "$1").replace(/^0+(?=\d)/, "");
14115
+ return stripFractionZeros(token.replace(/[^\d.]/g, "")).replace(/^0+(?=\d)/, "");
13142
14116
  }
13143
14117
  function computes(line) {
13144
14118
  const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX").replace(LITERAL_ARITH_RE, "N");
13145
14119
  return COMPUTE_RE.test(code) || RELATIONAL_RE.test(line);
13146
14120
  }
14121
+ function identifiersIn(line) {
14122
+ const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX");
14123
+ return [code, ...[...line.matchAll(TEMPLATE_EXPR_RE)].map((m) => m[1])].flatMap((part) => part.match(IDENTIFIER_RE) ?? []);
14124
+ }
14125
+ function addTyped(typed, value) {
14126
+ const d = digitsOf(value);
14127
+ if (/\d/.test(d))
14128
+ typed.add(d);
14129
+ }
13147
14130
  function reasonFor4(kind, label, value) {
13148
14131
  const what = kind === "derived-value-shape-only" ? `The ${label} is checked only for its shape, so any amount passes, including a wrongly computed one.` : `The ${label} is checked against a number written into the test (${value}). That number was read off the page, so when the page computes it wrong the check still passes.`;
13149
14132
  return `${what} Instead, ${COMPUTE_SHAPE} If the number is not computed from other values (a list price, an id, a fixed fee), leave it as it is. Keep the existing checks.`;
@@ -13155,25 +14138,32 @@ function analyzeDerivedValues(rawCode) {
13155
14138
  for (const body of testBodies(rawCode)) {
13156
14139
  if (findings.length >= MAX_FINDINGS4)
13157
14140
  break;
13158
- if (body.some(computes))
13159
- continue;
13160
14141
  const typed = /* @__PURE__ */ new Set();
13161
14142
  const definitions = /* @__PURE__ */ new Map();
14143
+ const computed = /* @__PURE__ */ new Set();
14144
+ const usesComputed = (text) => identifiersIn(text).some((id) => computed.has(id));
13162
14145
  for (const line of body) {
13163
14146
  if (FILL_RE2.test(line) && !EXPECT_RE3.test(line)) {
13164
- for (const m of line.matchAll(/['"`]([^'"`\n]{0,40})['"`]/g)) {
13165
- const d = digitsOf(m[1]);
13166
- if (/\d/.test(d))
13167
- typed.add(d);
13168
- }
14147
+ for (const m of line.matchAll(QUOTED_RE))
14148
+ addTyped(typed, m[1]);
14149
+ const arg = FILL_ARG_RE.exec(line)?.[1];
14150
+ const literal = arg === void 0 ? null : LITERAL_VALUE_RE.exec(definitions.get(arg) ?? "");
14151
+ const value = literal?.slice(1).find((v) => v !== void 0);
14152
+ if (value !== void 0)
14153
+ addTyped(typed, value);
13169
14154
  }
13170
14155
  const def = DEFINITION_RE.exec(line);
13171
- if (def)
14156
+ if (def) {
13172
14157
  definitions.set(def[1], def[2]);
14158
+ if (computes(def[2]) || usesComputed(def[2]))
14159
+ computed.add(def[1]);
14160
+ }
13173
14161
  }
13174
14162
  for (const line of body) {
13175
14163
  if (!EXPECT_RE3.test(line) || !VALUE_MATCHER_RE2.test(line) || /\.not\s{0,20}\./.test(line))
13176
14164
  continue;
14165
+ if (computes(line) || usesComputed(line))
14166
+ continue;
13177
14167
  const subject = EXPECT_SUBJECT_RE.exec(line)?.[1];
13178
14168
  const label = DERIVED_WORD_RE.exec(`${line} ${(subject && definitions.get(subject)) ?? ""}`)?.[0];
13179
14169
  if (!label)
@@ -13203,7 +14193,7 @@ function formatDerivedValueAdvisory(findings) {
13203
14193
  const where = findings.length === 1 ? "One computed value is" : `${findings.length} computed values are`;
13204
14194
  return `${where} checked against a copied number or only its shape (${short4(first.line, 90)}). ${first.reason}`;
13205
14195
  }
13206
- var MAX_FINDINGS4, EXPECT_RE3, DERIVED_WORD_RE, VALUE_MATCHER_RE2, MONEY_TOKEN_RE, SHAPE_ARG_RE, FILL_RE2, EXPECT_MESSAGE_RE, STRING_RE, REGEX_LITERAL_RE, COMPUTE_RE, RELATIONAL_RE, DEFINITION_RE, EXPECT_SUBJECT_RE, LITERAL_ARITH_RE, COMPUTE_SHAPE;
14196
+ var MAX_FINDINGS4, EXPECT_RE3, DERIVED_WORD_RE, VALUE_MATCHER_RE2, MONEY_TOKEN_RE, SHAPE_ARG_RE, FILL_RE2, EXPECT_MESSAGE_RE, STRING_RE, REGEX_LITERAL_RE, COMPUTE_RE, RELATIONAL_RE, DEFINITION_RE, EXPECT_SUBJECT_RE, LITERAL_ARITH_RE, TEMPLATE_EXPR_RE, IDENTIFIER_RE, QUOTED_RE, FILL_ARG_RE, LITERAL_VALUE_RE, COMPUTE_SHAPE;
13207
14197
  var init_derived_value_check = __esm({
13208
14198
  "../shared/dist/derived-value-check.js"() {
13209
14199
  "use strict";
@@ -13223,6 +14213,11 @@ var init_derived_value_check = __esm({
13223
14213
  DEFINITION_RE = /\b(?:const|let)\s{1,10}([\w$]{1,60})\s{0,10}=\s{0,10}([^\n]{0,300})/;
13224
14214
  EXPECT_SUBJECT_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}([\w$]{1,60})/;
13225
14215
  LITERAL_ARITH_RE = /\b\d[\d_.]{0,15}(?:\s{0,5}[*/+-]\s{0,5}\d[\d_.]{0,15}){1,8}\b/g;
14216
+ TEMPLATE_EXPR_RE = /\$\{([^{}\n]{0,200})\}/g;
14217
+ IDENTIFIER_RE = /[A-Za-z_$][\w$]{0,60}/g;
14218
+ QUOTED_RE = /['"`]([^'"`\n]{0,40})['"`]/g;
14219
+ FILL_ARG_RE = /\.(?:fill|type|pressSequentially|selectOption)\s{0,20}\(\s{0,20}([A-Za-z_$][\w$]{0,60})\s{0,20}[,)]/;
14220
+ LITERAL_VALUE_RE = /^\s{0,10}(?:'([^'\n]{0,40})'|"([^"\n]{0,40})"|`([^`$\n]{0,40})`|(\d[\d_,]{0,15}(?:\.\d{1,4})?))\s{0,10};?\s{0,10}$/;
13226
14221
  COMPUTE_SHAPE = "compute the expected value in the test from its inputs (the values the test typed, or each input read from the page) and compare: const toNumber = (s) => Number(s.replace(/[^\\d.-]/g, '')); const unit = toNumber(await <unit price locator>.innerText()); then expect(toNumber(await <shown value locator>.innerText())).toBeCloseTo(unit * quantity, 2); for several lines, read them all and sum them with reduce. For a comma-decimal price like \u20AC1.234,56, drop the dots and turn the comma into a point before Number, since the replace above would read it as 1.23456.";
13227
14222
  }
13228
14223
  });
@@ -13335,7 +14330,7 @@ function findExpectCalls(code) {
13335
14330
  if (!subj)
13336
14331
  continue;
13337
14332
  const rest = code.slice(subj.end + 1, subj.end + 1 + 80);
13338
- const mm = /^\s*((?:\.\s*not)?\s*\.\s*(\w+))\s{0,20}\(/.exec(rest);
14333
+ const mm = /^\s*((?:\.\s*not\s*)?\.\s*(\w+))\s{0,20}\(/.exec(rest);
13339
14334
  if (!mm)
13340
14335
  continue;
13341
14336
  const argStart = subj.end + 1 + mm[0].length;
@@ -15257,6 +16252,133 @@ function formatStaleAfterSubmitAdvisory(rawCode) {
15257
16252
  const more = described.length > 3 ? ` (+${described.length - 3} more)` : "";
15258
16253
  return `stale-after-submit: ${described.slice(0, 3).join("; ")}${more}; these pass before the request lands and still pass if the server rejects or ignores the save. Wait for the result instead: page.waitForResponse around the click, or assert something the submit creates (the new row, the confirmation message, a path with an end anchor such as /\\/orders\\/\\d+$/)`;
15259
16254
  }
16255
+ function regexLiteralEnd4(text, at) {
16256
+ let inClass = false;
16257
+ for (let i = at + 1; i < text.length; i++) {
16258
+ const ch = text[i];
16259
+ if (ch === "\n")
16260
+ return -1;
16261
+ if (ch === "\\") {
16262
+ i++;
16263
+ continue;
16264
+ }
16265
+ if (inClass) {
16266
+ if (ch === "]")
16267
+ inClass = false;
16268
+ continue;
16269
+ }
16270
+ if (ch === "[") {
16271
+ inClass = true;
16272
+ continue;
16273
+ }
16274
+ if (ch === "/")
16275
+ return i + 1;
16276
+ }
16277
+ return -1;
16278
+ }
16279
+ function startsWithRegexLiteral(text) {
16280
+ const t = text.trimStart();
16281
+ return t[0] === "/" && t[1] !== "/" && t[1] !== "*" && regexLiteralEnd4(t, 0) !== -1;
16282
+ }
16283
+ function isBareRegexLiteral(text) {
16284
+ const t = text.trimStart();
16285
+ if (t[0] !== "/" || t[1] === "/" || t[1] === "*")
16286
+ return false;
16287
+ const end = regexLiteralEnd4(t, 0);
16288
+ if (end === -1)
16289
+ return false;
16290
+ let i = end;
16291
+ while (i < t.length && /[a-z]/.test(t[i]))
16292
+ i++;
16293
+ while (i < t.length && /\s/.test(t[i]))
16294
+ i++;
16295
+ return i >= t.length || t[i] === ",";
16296
+ }
16297
+ function regexDeclaredNames(code) {
16298
+ const names = /* @__PURE__ */ new Set();
16299
+ 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;
16300
+ let m;
16301
+ while ((m = decl.exec(code)) !== null)
16302
+ names.add(m[1]);
16303
+ return names;
16304
+ }
16305
+ function afterFirstArg(args) {
16306
+ let depth = 0;
16307
+ let quote = null;
16308
+ for (let i = 0; i < args.length; i++) {
16309
+ const ch = args[i];
16310
+ if (quote) {
16311
+ if (ch === "\\") {
16312
+ i++;
16313
+ continue;
16314
+ }
16315
+ if (ch === quote)
16316
+ quote = null;
16317
+ continue;
16318
+ }
16319
+ if (ch === '"' || ch === "'" || ch === "`") {
16320
+ quote = ch;
16321
+ continue;
16322
+ }
16323
+ if (ch === "(" || ch === "[" || ch === "{")
16324
+ depth++;
16325
+ else if (ch === ")" || ch === "]" || ch === "}")
16326
+ depth--;
16327
+ else if (ch === "," && depth === 0)
16328
+ return args.slice(i + 1);
16329
+ }
16330
+ return "";
16331
+ }
16332
+ function analyzeRegexMatchers(rawCode) {
16333
+ if (!rawCode || typeof rawCode !== "string")
16334
+ return [];
16335
+ const code = stripComments2(rawCode.slice(0, MAX_LINT_INPUT));
16336
+ const out = [];
16337
+ const re = /\bexpect(?:\.soft)?\s{0,20}\(/g;
16338
+ let m;
16339
+ let examined = 0;
16340
+ let line = 1;
16341
+ let lineAt2 = 0;
16342
+ let regexNames;
16343
+ while ((m = re.exec(code)) !== null && examined++ < MAX_CALLS && out.length < MAX_REGEX_MATCHER_FINDINGS) {
16344
+ const subj = readBalanced(code, m.index + m[0].length);
16345
+ if (!subj)
16346
+ continue;
16347
+ const mm = /^\s{0,20}(\.\s{0,20}not)?\s{0,20}\.\s{0,20}(\w{1,40})\s{0,20}\(/.exec(code.slice(subj.end + 1, subj.end + 1 + 80));
16348
+ if (!mm || !REGEX_EQUALITY_MATCHERS.has(mm[2]))
16349
+ continue;
16350
+ const args = readBalanced(code, subj.end + 1 + mm[0].length);
16351
+ if (!args)
16352
+ continue;
16353
+ const expected = mm[2] === "toHaveJSProperty" ? afterFirstArg(args.text) : args.text;
16354
+ if (!isBareRegexLiteral(expected))
16355
+ continue;
16356
+ if (mm[2] !== "toHaveJSProperty" && mm[2] !== "toContain") {
16357
+ const subject = subj.text.trim();
16358
+ if (startsWithRegexLiteral(subject) || /^new\s+RegExp\b/.test(subject))
16359
+ continue;
16360
+ if (/^[A-Za-z_$][\w$]{0,80}$/.test(subject)) {
16361
+ regexNames ??= regexDeclaredNames(code);
16362
+ if (regexNames.has(subject))
16363
+ continue;
16364
+ }
16365
+ }
16366
+ for (let i = code.indexOf("\n", lineAt2); i !== -1 && i < m.index; i = code.indexOf("\n", i + 1)) {
16367
+ line++;
16368
+ lineAt2 = i + 1;
16369
+ }
16370
+ out.push({ line, matcher: mm[2], negated: mm[1] !== void 0 });
16371
+ }
16372
+ return out;
16373
+ }
16374
+ function formatRegexMatcherNote(findings) {
16375
+ const lines = findings.map((f) => {
16376
+ 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";
16377
+ 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"}.`;
16378
+ });
16379
+ return `${lines.join("\n")}
16380
+ 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.`;
16381
+ }
15260
16382
  function lintTestQuality(rawCode, opts = {}) {
15261
16383
  if (!rawCode || typeof rawCode !== "string")
15262
16384
  return [];
@@ -15264,6 +16386,10 @@ function lintTestQuality(rawCode, opts = {}) {
15264
16386
  return lintTestData(rawCode, { pageContacts: opts.pageContacts });
15265
16387
  if (opts.only === "test-data-blocking")
15266
16388
  return analyzeTestData(rawCode, { pageContacts: opts.pageContacts }).blocking;
16389
+ if (opts.only === "regex-matcher") {
16390
+ const found = analyzeRegexMatchers(rawCode);
16391
+ return found.length > 0 ? [formatRegexMatcherNote(found)] : [];
16392
+ }
15267
16393
  const warnings = [];
15268
16394
  if (rawCode.length > MAX_LINT_INPUT) {
15269
16395
  warnings.push(`The code is ${Math.round(rawCode.length / 1024)} KB; the quality lint checked only the first ${MAX_LINT_INPUT / 1e3} KB. Split it into smaller tests (one behaviour per test).`);
@@ -15538,7 +16664,7 @@ function specItemsFromTestPlans(testPlans) {
15538
16664
  }
15539
16665
  return [...new Set(out)].slice(0, 200);
15540
16666
  }
15541
- var READ_ONLY_TAGS, PRODUCTION_READ_ONLY_GUARD_TEMPLATE, HOST_AGNOSTIC_URL_TEMPLATE, TEST_QUALITY_CONVENTIONS, NON_PROD_HOST_LABEL, NON_PROD_HOST_TOKEN, TRANSIENT_TEXT, STRING_LITERAL, FIRST_LITERAL, GET_BY_TEXT_LITERAL2, ROLE_NAME_LITERAL2, MAX_LINT_INPUT, MAX_BALANCED_SCAN, MAX_CALLS, MAX_SEEN_SCAN, ACTION_CALL, BY_NAME_LOCATOR, ACTION_AFTER, MAX_CONTACTS, MAX_SNAPSHOT_SCAN, PHONE_IN_TEXT, EMAIL_IN_TEXT, TEL_HREF, MAILTO_HREF, FIELD_LINE, INBOX_EMAIL_READ, MAIL_READ_CALL, RESERVED_EMAIL_TLD, PHONE_LABEL, NON_PHONE_NUMBER_LABEL, IDENTITY_LABEL, LOOKUP_LABEL, NON_NAME_SUFFIX_LABEL, DATE_LIKE, SIGNUP_ACTION_NAME, SIGNUP_SUBMIT_NAME, LOGIN_ACTION_NAME, SIGNUP_GOTO, LOGIN_GOTO, MINTED_INBOX_ALIAS, STAMP_EVAL, RECORDING_VALUE_PLACEHOLDERS, NEGATIVE_TEST_TITLE, NEGATIVE_SIGNUP_ASSERT, STORED_LOGIN_RE, CONFIRM_CLICK_RE, SAVE_UPDATE_RE, NOT_A_CONFIRM_RE, ENTITY_WORDS, CREATE_ENTITY_CLICK_RE, RESTORE_CLICK_RE, MAX_ACCOUNT_ACTIONS, MAX_TRY_BLOCKS, MAX_STATEMENT_LOOKBACK, MAX_FINDINGS5, MAX_TRY_BODY, AUTH_REGION_START, AUTH_REGION_END, INBOX_REGION_START, INBOX_REGION_END, ACCOUNT_ROUTE_RE, ENTITY_WORD_RES, PERSIST_CALL_RE, FILL_IDENT_ARG_RE, PERSIST_ARGS_RE, HOST_IN_URL_ARG, URL_WAIT_TOKEN, GLOB_ESCAPED, URL_WAIT_MAX_PATH, URL_WAIT_MAX_GLOB_STARS, UNREACHED_MAX_PATHS, UNREACHED_MATCH_BUDGET, STALE_TOKEN, RESULT_WAIT, WRITE_CALL_PRESENT, SUBMIT_CALL_PRESENT, TEST_CALL, VALUE_WRITE_CALLS, SUBMIT_CALLS, DISTINCT_PAGE_CALLS, OWN_CONTROL_MATCHERS, OUTCOME_CONTENT_MATCHERS, OUTCOME_STATE_MATCHERS, ANY_CONTENT_ARG, ROOT_SUBJECT, LITERAL_SUBJECT, MATCHER_AFTER_EXPECT, STALE_MAX_EVENTS, CHECKED, UNCHECKED, squash, NOT_JUDGED, STALE_REASON_TEXT, LIVE_WRITE_NAME, BUNDLED_CHECKS_BEFORE_WRITE, STOP, SYNONYMS, AREA_TOKENS;
16667
+ var READ_ONLY_TAGS, PRODUCTION_READ_ONLY_GUARD_TEMPLATE, HOST_AGNOSTIC_URL_TEMPLATE, TEST_QUALITY_CONVENTIONS, NON_PROD_HOST_LABEL, NON_PROD_HOST_TOKEN, TRANSIENT_TEXT, STRING_LITERAL, FIRST_LITERAL, GET_BY_TEXT_LITERAL2, ROLE_NAME_LITERAL2, MAX_LINT_INPUT, MAX_BALANCED_SCAN, MAX_CALLS, MAX_SEEN_SCAN, ACTION_CALL, BY_NAME_LOCATOR, ACTION_AFTER, MAX_CONTACTS, MAX_SNAPSHOT_SCAN, PHONE_IN_TEXT, EMAIL_IN_TEXT, TEL_HREF, MAILTO_HREF, FIELD_LINE, INBOX_EMAIL_READ, MAIL_READ_CALL, RESERVED_EMAIL_TLD, PHONE_LABEL, NON_PHONE_NUMBER_LABEL, IDENTITY_LABEL, LOOKUP_LABEL, NON_NAME_SUFFIX_LABEL, DATE_LIKE, SIGNUP_ACTION_NAME, SIGNUP_SUBMIT_NAME, LOGIN_ACTION_NAME, SIGNUP_GOTO, LOGIN_GOTO, MINTED_INBOX_ALIAS, STAMP_EVAL, RECORDING_VALUE_PLACEHOLDERS, NEGATIVE_TEST_TITLE, NEGATIVE_SIGNUP_ASSERT, STORED_LOGIN_RE, CONFIRM_CLICK_RE, SAVE_UPDATE_RE, NOT_A_CONFIRM_RE, ENTITY_WORDS, CREATE_ENTITY_CLICK_RE, RESTORE_CLICK_RE, MAX_ACCOUNT_ACTIONS, MAX_TRY_BLOCKS, MAX_STATEMENT_LOOKBACK, MAX_FINDINGS5, MAX_TRY_BODY, AUTH_REGION_START, AUTH_REGION_END, INBOX_REGION_START, INBOX_REGION_END, ACCOUNT_ROUTE_RE, ENTITY_WORD_RES, PERSIST_CALL_RE, FILL_IDENT_ARG_RE, PERSIST_ARGS_RE, HOST_IN_URL_ARG, URL_WAIT_TOKEN, GLOB_ESCAPED, URL_WAIT_MAX_PATH, URL_WAIT_MAX_GLOB_STARS, UNREACHED_MAX_PATHS, UNREACHED_MATCH_BUDGET, STALE_TOKEN, RESULT_WAIT, WRITE_CALL_PRESENT, SUBMIT_CALL_PRESENT, TEST_CALL, VALUE_WRITE_CALLS, SUBMIT_CALLS, DISTINCT_PAGE_CALLS, OWN_CONTROL_MATCHERS, OUTCOME_CONTENT_MATCHERS, OUTCOME_STATE_MATCHERS, ANY_CONTENT_ARG, ROOT_SUBJECT, LITERAL_SUBJECT, MATCHER_AFTER_EXPECT, STALE_MAX_EVENTS, CHECKED, UNCHECKED, squash, NOT_JUDGED, STALE_REASON_TEXT, LIVE_WRITE_NAME, BUNDLED_CHECKS_BEFORE_WRITE, MAX_REGEX_MATCHER_FINDINGS, REGEX_EQUALITY_MATCHERS, STOP, SYNONYMS, AREA_TOKENS;
15542
16668
  var init_test_authoring_quality = __esm({
15543
16669
  "../shared/dist/test-authoring-quality.js"() {
15544
16670
  "use strict";
@@ -15770,6 +16896,8 @@ var init_test_authoring_quality = __esm({
15770
16896
  };
15771
16897
  LIVE_WRITE_NAME = /\b(?:submit|place|book|reserve|confirm|pay|send|checkout|order|register|sign\s?up|create account)\b/i;
15772
16898
  BUNDLED_CHECKS_BEFORE_WRITE = 5;
16899
+ MAX_REGEX_MATCHER_FINDINGS = 10;
16900
+ REGEX_EQUALITY_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual", "toContain", "toHaveJSProperty"]);
15773
16901
  STOP = /* @__PURE__ */ new Set([
15774
16902
  "a",
15775
16903
  "an",
@@ -16008,7 +17136,7 @@ function normalizeBugPath(route) {
16008
17136
  if (hash >= 0 && path[hash + 1] !== "/")
16009
17137
  path = path.slice(0, hash);
16010
17138
  const segments = path.toLowerCase().split("/").map((segment) => /\d/.test(segment) ? ":id" : segment);
16011
- const joined = segments.join("/").replace(/\/+$/, "");
17139
+ const joined = stripTrailingSlashes(segments.join("/"));
16012
17140
  return joined || (path.startsWith("/") ? "/" : "");
16013
17141
  }
16014
17142
  function isGenericConsoleMessage(message) {
@@ -16021,14 +17149,17 @@ function requestSignatures(text) {
16021
17149
  const statuses = [...text.matchAll(HTTP_STATUS)].map((m) => ({ status: m[1], at: m.index ?? 0, end: (m.index ?? 0) + m[0].length }));
16022
17150
  if (statuses.length === 0)
16023
17151
  return [];
16024
- const paths = [...text.matchAll(PATH_IN_TEXT)].map((m) => ({
16025
- method: m[1] ?? null,
16026
- path: normalizeBugPath((m[2] ?? m[3] ?? "").replace(/[.,;:]+$/, "")),
16027
- // Before normalisation, which turns a segment with a digit (favicon-32x32.png) into :id.
16028
- incidental: isIncidentalAssetPath((m[2] ?? m[3] ?? "").replace(/[.,;:]+$/, "").split(/[?#]/, 1)[0].toLowerCase()),
16029
- at: m.index ?? 0,
16030
- end: (m.index ?? 0) + m[0].length
16031
- })).filter((p) => p.path.length > 1);
17152
+ const paths = [...text.matchAll(PATH_IN_TEXT)].map((m) => {
17153
+ const rawPath = stripTrailingChars(m[2] ?? m[3] ?? "", PATH_TRAILING_PUNCTUATION);
17154
+ return {
17155
+ method: m[1] ?? null,
17156
+ path: normalizeBugPath(rawPath),
17157
+ // Before normalisation, which turns a segment with a digit (favicon-32x32.png) into :id.
17158
+ incidental: isIncidentalAssetPath(rawPath.split(/[?#]/, 1)[0].toLowerCase()),
17159
+ at: m.index ?? 0,
17160
+ end: (m.index ?? 0) + m[0].length
17161
+ };
17162
+ }).filter((p) => p.path.length > 1);
16032
17163
  const out = /* @__PURE__ */ new Map();
16033
17164
  for (const { status, at, end } of statuses) {
16034
17165
  const lead = STATUS_TO_PATH.exec(text.slice(end, end + 40));
@@ -16138,10 +17269,12 @@ function dedupeSiteWideSuspectedBugs(drafts) {
16138
17269
  delete plans.suspectedBugs;
16139
17270
  }
16140
17271
  }
16141
- 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;
17272
+ 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;
16142
17273
  var init_survey_bug_signature = __esm({
16143
17274
  "../shared/dist/survey-bug-signature.js"() {
16144
17275
  "use strict";
17276
+ init_trailing_slashes();
17277
+ PATH_TRAILING_PUNCTUATION = /[.,;:]/;
16145
17278
  SIGNATURE_SOURCE_MAX_CHARS = 800;
16146
17279
  UUID = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g;
16147
17280
  PREFIXED_ID = /\b[a-z]{1,8}[-_]\d+\b/g;
@@ -17422,9 +18555,9 @@ function lastLocatorLabels(chain) {
17422
18555
  if (t)
17423
18556
  labels.push(t[1]);
17424
18557
  }
17425
- const has = optionText(last.args, HAS_TEXT_OPTION_RE);
17426
- if (has !== null)
17427
- labels.push(has);
18558
+ const has2 = optionText(last.args, HAS_TEXT_OPTION_RE);
18559
+ if (has2 !== null)
18560
+ labels.push(has2);
17428
18561
  }
17429
18562
  return labels.map((l) => l.replace(/^[\s^(?:]+/, "").trim());
17430
18563
  }
@@ -17472,7 +18605,7 @@ function pathIdsIn(text) {
17472
18605
  function findCounterIdPins(code) {
17473
18606
  if (code.length > CLOCK_MAX_CODE_CHARS)
17474
18607
  return [];
17475
- const clean2 = stripLineComments(blankBlockComments3(code));
18608
+ const clean3 = stripLineComments(blankBlockComments3(code));
17476
18609
  const fillValues = [];
17477
18610
  const candidates = [];
17478
18611
  let seenWrite = false;
@@ -17480,24 +18613,24 @@ function findCounterIdPins(code) {
17480
18613
  CALL_RE.lastIndex = 0;
17481
18614
  let m;
17482
18615
  let calls = 0;
17483
- while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean2)) !== null) {
18616
+ while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean3)) !== null) {
17484
18617
  const name = m[1];
17485
18618
  const open = m.index + m[0].length - 1;
17486
18619
  if (VALUE_WRITES.has(name)) {
17487
18620
  seenWrite = true;
17488
- const args2 = argsAt(clean2, open);
18621
+ const args2 = argsAt(clean3, open);
17489
18622
  if (args2 !== null)
17490
18623
  fillValues.push(...staticStrings(args2));
17491
18624
  continue;
17492
18625
  }
17493
18626
  if (createIndex === -1) {
17494
- if (name === "click" && seenWrite && isCreateClick(clean2, m.index))
18627
+ if (name === "click" && seenWrite && isCreateClick(clean3, m.index))
17495
18628
  createIndex = m.index;
17496
18629
  continue;
17497
18630
  }
17498
18631
  if (name === "click")
17499
18632
  continue;
17500
- const args = argsAt(clean2, open);
18633
+ const args = argsAt(clean3, open);
17501
18634
  if (args === null)
17502
18635
  continue;
17503
18636
  const isUrl = URL_SINKS.has(name);
@@ -17516,7 +18649,7 @@ function findCounterIdPins(code) {
17516
18649
  }
17517
18650
  if (createIndex === -1)
17518
18651
  return [];
17519
- const before = clean2.slice(0, createIndex);
18652
+ const before = clean3.slice(0, createIndex);
17520
18653
  let pathIdsBefore = null;
17521
18654
  const findings = [];
17522
18655
  const seen = /* @__PURE__ */ new Set();
@@ -17540,7 +18673,7 @@ function findCounterIdPins(code) {
17540
18673
  continue;
17541
18674
  }
17542
18675
  }
17543
- findings.push({ kind: c.kind, literal: c.literal, line: lineAt(clean2, c.index) });
18676
+ findings.push({ kind: c.kind, literal: c.literal, line: lineAt(clean3, c.index) });
17544
18677
  }
17545
18678
  return findings;
17546
18679
  }
@@ -17835,7 +18968,7 @@ var init_suspicious_expectation = __esm({
17835
18968
  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;
17836
18969
  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;
17837
18970
  STUCK_VETO_RE = new RegExp(`${NOT_STUCK_RE.source}|${LISTS_STUCK_RE.source}`, "i");
17838
- 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;
18971
+ 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;
17839
18972
  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)";
17840
18973
  ELLIPSIS = "(?:\\.{3}|\u2026)";
17841
18974
  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;
@@ -17991,7 +19124,7 @@ function outcomeNamedByAssertion(outcome, code) {
17991
19124
  function namedByStatements(outcome, statements) {
17992
19125
  const text = outcomeText(outcome);
17993
19126
  if (outcome.type === "url") {
17994
- const routes = (text.match(ROUTE_TERM) ?? []).map((r) => r.replace(/[.,;:!?)]+$/, "")).filter((r) => r.length > 1);
19127
+ const routes = (text.match(ROUTE_TERM) ?? []).map((r) => stripTrailingChars(r, ROUTE_TRAILING_PUNCTUATION)).filter((r) => r.length > 1);
17995
19128
  return statements.some((stmt) => {
17996
19129
  if (!/\.(?:waitForURL|toHaveURL)\s{0,20}\(/.test(stmt))
17997
19130
  return false;
@@ -18163,7 +19296,7 @@ function evaluatePromotionGate(input, checks = PROMOTION_QUALITY_CHECKS) {
18163
19296
  ...suspiciousTagged ? { suspiciousPass: { mode: suspicious, covered: suspiciousCovered, wouldHold: suspiciousHold } } : {}
18164
19297
  };
18165
19298
  }
18166
- 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;
19299
+ 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;
18167
19300
  var init_promotion_quality_gate = __esm({
18168
19301
  "../shared/dist/promotion-quality-gate.js"() {
18169
19302
  "use strict";
@@ -18178,17 +19311,21 @@ var init_promotion_quality_gate = __esm({
18178
19311
  init_run_classification();
18179
19312
  init_expected_outcome_coverage();
18180
19313
  init_expect_call();
19314
+ init_trailing_slashes();
18181
19315
  SOURCE_RANK = { generator: 0, classifier: 0, healer: 1, human: 2 };
18182
19316
  strengthCheck = {
18183
19317
  id: "assertion-strength",
18184
- run({ code, strengthGate, flagContradictedAdditions }) {
18185
- const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true });
19318
+ run({ code, tags, strengthGate, flagContradictedAdditions, lastActionProof }) {
19319
+ const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true, lastActionProof });
18186
19320
  if (verdict.severity === "route-bug")
18187
19321
  return [{ check: "route-bug" }];
18188
- if (verdict.severity !== "block")
19322
+ const authDowngrade = verdict.severity === "downgrade" && tags.some((t) => t.toLowerCase() === "@auth");
19323
+ if (verdict.severity !== "block" && !authDowngrade)
18189
19324
  return [];
18190
19325
  const revealed = strengthGate?.severity === "none" && strengthGate.revealEvidence === "revealed" && strengthGate.source !== "heal";
18191
- return revealed ? [] : [{ check: "strength-block" }];
19326
+ if (revealed)
19327
+ return [];
19328
+ return [{ check: authDowngrade ? "strength-downgrade-auth" : "strength-block" }];
18192
19329
  }
18193
19330
  };
18194
19331
  STRING_CONST = /\b(?:const|let|var)\s{1,20}([A-Za-z_$][\w$]{0,60})\s{0,20}=\s{0,20}(?:'([^'\\\n]{0,300})'|"([^"\\\n]{0,300})"|`([^`\\$\n]{0,300})`)\s{0,20}[;\n]/g;
@@ -18197,7 +19334,7 @@ var init_promotion_quality_gate = __esm({
18197
19334
  MAX_STATEMENTS = 500;
18198
19335
  MAX_STATEMENT_CHARS = 400;
18199
19336
  LITERAL_OR_IDENTIFIER = /'(?:[^'\\\n]|\\.){0,500}'|"(?:[^"\\\n]|\\.){0,500}"|`(?:[^`\\]|\\.){0,2000}`|(?<![\w$.])[A-Za-z_$][\w$]{0,60}/g;
18200
- 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}\(/;
19337
+ 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}\(/;
18201
19338
  FEEDBACK_REGION = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/i;
18202
19339
  ONE_SHOT_MESSAGE_REASON = "one-shot success/error banner text";
18203
19340
  FEEDBACK_REGION_ALL = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/gi;
@@ -18256,6 +19393,7 @@ var init_promotion_quality_gate = __esm({
18256
19393
  "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}\)/
18257
19394
  };
18258
19395
  ROUTE_TERM = /\/[A-Za-z0-9_./?&=#-]{1,200}/g;
19396
+ ROUTE_TRAILING_PUNCTUATION = /[.,;:!?)]/;
18259
19397
  QUOTED_LITERAL = /'([^'\\\n]{3,200})'|"([^"\\\n]{3,200})"|`([^`\\$\n]{3,200})`/g;
18260
19398
  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;
18261
19399
  MAX_OUTCOME_STATEMENTS = 300;
@@ -18375,8 +19513,8 @@ ${goalText}`);
18375
19513
  return count3 > 0 ? [{ check: "locator-fragment", detail: String(count3), soft: true }] : [];
18376
19514
  }
18377
19515
  };
18378
- 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;
18379
- NEXT_ACTION = /\.(?:goto|reload|click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|selectOption|setInputFiles|dragTo|clear)\s*\(/;
19516
+ 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;
19517
+ NEXT_ACTION = /\.(?:goto|reload|click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|setChecked|selectOption|setInputFiles|dragTo|dragAndDrop|clear)\s*\(/;
18380
19518
  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;
18381
19519
  MAX_FILTER_LINES = 2e3;
18382
19520
  MAX_FILTER_LINE_CHARS = 600;
@@ -18491,7 +19629,7 @@ ${goalText}`);
18491
19629
  });
18492
19630
 
18493
19631
  // ../shared/dist/regex-alternation.js
18494
- function closingParens(source) {
19632
+ function closingParens2(source) {
18495
19633
  const close = new Int32Array(source.length).fill(-1);
18496
19634
  const open = [];
18497
19635
  let inClass = false;
@@ -18516,7 +19654,7 @@ function closingParens(source) {
18516
19654
  return close;
18517
19655
  }
18518
19656
  function alternatesAtTop(source) {
18519
- const close = closingParens(source);
19657
+ const close = closingParens2(source);
18520
19658
  let start = source.startsWith("^") ? 1 : 0;
18521
19659
  let end = source.endsWith("$") ? source.length - 1 : source.length;
18522
19660
  while (start < end && source[start] === "(" && close[start] === end - 1) {
@@ -18565,6 +19703,124 @@ var init_page_evidence = __esm({
18565
19703
  }
18566
19704
  });
18567
19705
 
19706
+ // ../shared/dist/stated-limits.js
19707
+ function validDate(v) {
19708
+ const [y, m, d] = v.split("-").map(Number);
19709
+ const t = new Date(Date.UTC(y, m - 1, d));
19710
+ return t.getUTCFullYear() === y && t.getUTCMonth() === m - 1 && t.getUTCDate() === d;
19711
+ }
19712
+ function isoFor(type, v) {
19713
+ const re = DATE_RE[type];
19714
+ if (!re || !re.test(v))
19715
+ return false;
19716
+ if (type === "date")
19717
+ return validDate(v);
19718
+ if (type === "datetime-local")
19719
+ return validDate(v.slice(0, 10)) && Number(v.slice(11, 13)) < 24 && Number(v.slice(14, 16)) < 60;
19720
+ if (type === "time")
19721
+ return Number(v.slice(0, 2)) < 24 && Number(v.slice(3, 5)) < 60;
19722
+ if (type === "month")
19723
+ return Number(v.slice(5, 7)) >= 1 && Number(v.slice(5, 7)) <= 12;
19724
+ return Number(v.slice(6, 8)) >= 1 && Number(v.slice(6, 8)) <= 53;
19725
+ }
19726
+ function clean2(raw) {
19727
+ return typeof raw === "string" ? raw.trim() : "";
19728
+ }
19729
+ function statedLimitsFromAttrs(attrs, inputType) {
19730
+ const type = (inputType ?? "").trim().toLowerCase();
19731
+ const limits = [];
19732
+ const add = (k, v) => {
19733
+ limits.push({ k, v });
19734
+ };
19735
+ if (NUMBER_TYPES.has(type)) {
19736
+ for (const [attr, k] of [["min", "min"], ["max", "max"]]) {
19737
+ const v = clean2(attrs[attr]);
19738
+ if (NUMBER_RE.test(v))
19739
+ add(k, v);
19740
+ }
19741
+ const step = clean2(attrs.step);
19742
+ if (NUMBER_RE.test(step) && Number(step) > 0)
19743
+ add("step", step);
19744
+ } else if (type in DATE_RE) {
19745
+ for (const [attr, k] of [["min", "min"], ["max", "max"]]) {
19746
+ const v = clean2(attrs[attr]);
19747
+ if (isoFor(type, v))
19748
+ add(k, v);
19749
+ }
19750
+ } else if (TEXT_TYPES.has(type)) {
19751
+ for (const [attr, k] of [["minlength", "minLength"], ["maxlength", "maxLength"]]) {
19752
+ const v = clean2(attrs[attr]);
19753
+ if (LENGTH_RE.test(v))
19754
+ add(k, String(Number(v)));
19755
+ }
19756
+ const pattern = cleanPattern(clean2(attrs.pattern).slice(0, STATED_PATTERN_MAX));
19757
+ if (pattern)
19758
+ add("pattern", pattern);
19759
+ }
19760
+ return limits.length > 0 ? { v: STATED_LIMITS_VERSION, limits } : null;
19761
+ }
19762
+ function cleanPattern(text) {
19763
+ return text.replace(/[\u0000-\u001f\u007f]/g, "").replace(/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g, "");
19764
+ }
19765
+ function readStatedLimits(stored) {
19766
+ if (!stored || typeof stored !== "object" || Array.isArray(stored))
19767
+ return [];
19768
+ const rec = stored;
19769
+ if (rec.v !== STATED_LIMITS_VERSION || !Array.isArray(rec.limits))
19770
+ return [];
19771
+ const out = [];
19772
+ const seen = /* @__PURE__ */ new Set();
19773
+ for (const item of rec.limits.slice(0, MAX_LIMITS * 2)) {
19774
+ if (!item || typeof item !== "object")
19775
+ continue;
19776
+ const { k, v } = item;
19777
+ if (typeof k !== "string" || typeof v !== "string" || !KINDS.includes(k) || seen.has(k))
19778
+ continue;
19779
+ if (v.length === 0 || v.length > STATED_PATTERN_MAX)
19780
+ continue;
19781
+ const text = k === "pattern" ? cleanPattern(v) : v;
19782
+ if (text === "")
19783
+ continue;
19784
+ if (k === "minLength" || k === "maxLength") {
19785
+ if (!LENGTH_RE.test(v))
19786
+ continue;
19787
+ } else if (k === "step") {
19788
+ if (!NUMBER_RE.test(v))
19789
+ continue;
19790
+ } else if (k === "min" || k === "max") {
19791
+ if (!NUMBER_RE.test(v) && !Object.values(DATE_RE).some((re) => re.test(v)))
19792
+ continue;
19793
+ }
19794
+ seen.add(k);
19795
+ out.push({ k, v: text });
19796
+ }
19797
+ return out;
19798
+ }
19799
+ function statedLimitsRecord(limits) {
19800
+ return limits.length > 0 ? { v: STATED_LIMITS_VERSION, limits } : null;
19801
+ }
19802
+ var STATED_LIMITS_VERSION, STATED_PATTERN_MAX, KINDS, MAX_LIMITS, TEXT_TYPES, NUMBER_TYPES, DATE_RE, NUMBER_RE, LENGTH_RE;
19803
+ var init_stated_limits = __esm({
19804
+ "../shared/dist/stated-limits.js"() {
19805
+ "use strict";
19806
+ STATED_LIMITS_VERSION = 1;
19807
+ STATED_PATTERN_MAX = 200;
19808
+ KINDS = ["min", "max", "minLength", "maxLength", "step", "pattern"];
19809
+ MAX_LIMITS = KINDS.length;
19810
+ TEXT_TYPES = /* @__PURE__ */ new Set(["text", "search", "email", "url", "tel", "password", "textarea", ""]);
19811
+ NUMBER_TYPES = /* @__PURE__ */ new Set(["number", "range"]);
19812
+ DATE_RE = {
19813
+ date: /^\d{4}-\d{2}-\d{2}$/,
19814
+ "datetime-local": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?$/,
19815
+ time: /^\d{2}:\d{2}(?::\d{2})?$/,
19816
+ month: /^\d{4}-\d{2}$/,
19817
+ week: /^\d{4}-W\d{2}$/
19818
+ };
19819
+ NUMBER_RE = /^-?\d{1,15}(?:\.\d{1,10})?$/;
19820
+ LENGTH_RE = /^\d{1,7}$/;
19821
+ }
19822
+ });
19823
+
18568
19824
  // ../shared/dist/index.js
18569
19825
  var dist_exports = {};
18570
19826
  __export(dist_exports, {
@@ -18572,9 +19828,11 @@ __export(dist_exports, {
18572
19828
  ACTION_BATCH_MAX_SIZE: () => ACTION_BATCH_MAX_SIZE,
18573
19829
  ADDON_DEFINITIONS: () => ADDON_DEFINITIONS,
18574
19830
  ADMIN_EMAIL_LIMITS: () => ADMIN_EMAIL_LIMITS,
19831
+ ADMIN_FORCE_CANCELLED_RUN_ERROR: () => ADMIN_FORCE_CANCELLED_RUN_ERROR,
18575
19832
  ADMIN_VIEW_DEFAULT_RETURN_TO: () => ADMIN_VIEW_DEFAULT_RETURN_TO,
18576
19833
  AGENT_AI_PROVIDER_PREFIXES: () => AGENT_AI_PROVIDER_PREFIXES,
18577
19834
  AI_CREDIT_MARGIN: () => AI_CREDIT_MARGIN,
19835
+ ALLOWED_PROVIDER_CHAIN_HOPS: () => ALLOWED_PROVIDER_CHAIN_HOPS,
18578
19836
  API_AUTH_HEADERS_ENV_KEY: () => API_AUTH_HEADERS_ENV_KEY,
18579
19837
  API_MODEL_CATALOG: () => API_MODEL_CATALOG,
18580
19838
  API_MODEL_FIELDS: () => API_MODEL_FIELDS,
@@ -18595,6 +19853,7 @@ __export(dist_exports, {
18595
19853
  AUDIO_CHUNK_DURATION_MS: () => AUDIO_CHUNK_DURATION_MS,
18596
19854
  AUTH_PATH_SIGNATURE: () => AUTH_PATH_SIGNATURE,
18597
19855
  AUTH_PROVIDER_HOST_SIGNATURES: () => AUTH_PROVIDER_HOST_SIGNATURES,
19856
+ AUTH_SESSION_GENERATION_RE: () => AUTH_SESSION_GENERATION_RE,
18598
19857
  AUTH_STATE_EXPIRY_SKEW_MS: () => AUTH_STATE_EXPIRY_SKEW_MS,
18599
19858
  AUTH_TAG_COOKIE_STATE: () => AUTH_TAG_COOKIE_STATE,
18600
19859
  AUTH_TAG_LOGIN_BEARER: () => AUTH_TAG_LOGIN_BEARER,
@@ -18613,12 +19872,14 @@ __export(dist_exports, {
18613
19872
  BUG_TEST_BROKEN_REASON: () => BUG_TEST_BROKEN_REASON,
18614
19873
  BUG_TEST_BROKEN_TAG: () => BUG_TEST_BROKEN_TAG,
18615
19874
  CACHE_PRICE_PER_M: () => CACHE_PRICE_PER_M,
19875
+ CANCELLED_REASON_PREFIXES: () => CANCELLED_REASON_PREFIXES,
18616
19876
  CANVAS_UNSUPPORTED_BODY: () => CANVAS_UNSUPPORTED_BODY,
18617
19877
  CANVAS_UNSUPPORTED_CODE: () => CANVAS_UNSUPPORTED_CODE,
18618
19878
  CANVAS_UNSUPPORTED_NOT_CHARGED: () => CANVAS_UNSUPPORTED_NOT_CHARGED,
18619
19879
  CANVAS_UNSUPPORTED_OVERRIDE_HINT: () => CANVAS_UNSUPPORTED_OVERRIDE_HINT,
18620
19880
  CANVAS_UNSUPPORTED_TITLE: () => CANVAS_UNSUPPORTED_TITLE,
18621
19881
  CAPTURE_JSON_STRUCTURE_LIMITS: () => CAPTURE_JSON_STRUCTURE_LIMITS,
19882
+ CHAIN_CANCELLED_RUN_ERROR: () => CHAIN_CANCELLED_RUN_ERROR,
18622
19883
  CHAT_FAILOVER_KEY: () => CHAT_FAILOVER_KEY,
18623
19884
  CLAUDE_CLI_DEFAULT_EFFORT: () => CLAUDE_CLI_DEFAULT_EFFORT,
18624
19885
  CLAUDE_CLI_DEFAULT_MODEL: () => CLAUDE_CLI_DEFAULT_MODEL,
@@ -18655,6 +19916,7 @@ __export(dist_exports, {
18655
19916
  DEFAULT_GEMINI_SCREEN_MODEL: () => DEFAULT_GEMINI_SCREEN_MODEL,
18656
19917
  DEFAULT_JSON_STRUCTURE_LIMITS: () => DEFAULT_JSON_STRUCTURE_LIMITS,
18657
19918
  DEFAULT_PLATFORM_DB_POOL_SIZE: () => DEFAULT_PLATFORM_DB_POOL_SIZE,
19919
+ DEFAULT_PROVIDER_ACCOUNT_ID: () => DEFAULT_PROVIDER_ACCOUNT_ID,
18658
19920
  DEFAULT_SHARED_DB_POOL_SIZE: () => DEFAULT_SHARED_DB_POOL_SIZE,
18659
19921
  DEFAULT_STT_MODEL: () => DEFAULT_STT_MODEL,
18660
19922
  DEFAULT_STT_MODEL_BY_PROVIDER: () => DEFAULT_STT_MODEL_BY_PROVIDER,
@@ -18671,6 +19933,7 @@ __export(dist_exports, {
18671
19933
  ENVIRONMENTAL_ERROR_STORED_PREFIXES: () => ENVIRONMENTAL_ERROR_STORED_PREFIXES,
18672
19934
  EVIDENCE_NOTE_PREFIXES: () => EVIDENCE_NOTE_PREFIXES,
18673
19935
  EXPECT_CALL_RE: () => EXPECT_CALL_RE,
19936
+ FEATURE_ROW_RUN_WINDOW: () => FEATURE_ROW_RUN_WINDOW,
18674
19937
  FLOW_PHASE_LABELS: () => FLOW_PHASE_LABELS,
18675
19938
  FLOW_PHASE_TYPES: () => FLOW_PHASE_TYPES,
18676
19939
  FLOW_SERVER_TYPES: () => FLOW_SERVER_TYPES,
@@ -18727,6 +19990,8 @@ __export(dist_exports, {
18727
19990
  LIVE_FRAME_STALE_MS: () => LIVE_FRAME_STALE_MS,
18728
19991
  LIVE_FRAME_UNFOCUSED_INTERVAL_MS: () => LIVE_FRAME_UNFOCUSED_INTERVAL_MS,
18729
19992
  LIVE_THUMBNAIL_MAX_BASE64_BYTES: () => LIVE_THUMBNAIL_MAX_BASE64_BYTES,
19993
+ MAX_BALANCED_SCAN: () => MAX_BALANCED_SCAN,
19994
+ MAX_CALLS: () => MAX_CALLS,
18730
19995
  MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
18731
19996
  MAX_DEEPLINK_LENGTH: () => MAX_DEEPLINK_LENGTH,
18732
19997
  MAX_EMAIL_ADDRESS_LENGTH: () => MAX_EMAIL_ADDRESS_LENGTH,
@@ -18736,6 +20001,8 @@ __export(dist_exports, {
18736
20001
  MAX_ORG_NAME_LENGTH: () => MAX_ORG_NAME_LENGTH,
18737
20002
  MAX_PASSWORD_BYTES: () => MAX_PASSWORD_BYTES,
18738
20003
  MAX_PLAYWRIGHT_CODE_LENGTH: () => MAX_PLAYWRIGHT_CODE_LENGTH,
20004
+ MAX_PROVIDER_ACCOUNTS: () => MAX_PROVIDER_ACCOUNTS,
20005
+ MAX_PROVIDER_ACCOUNT_MODELS: () => MAX_PROVIDER_ACCOUNT_MODELS,
18739
20006
  MAX_SURVEY_ENTRY_URLS: () => MAX_SURVEY_ENTRY_URLS,
18740
20007
  MAX_SURVEY_ENTRY_URL_LENGTH: () => MAX_SURVEY_ENTRY_URL_LENGTH,
18741
20008
  MCP_ARMY_LIMITS: () => MCP_ARMY_LIMITS,
@@ -18771,6 +20038,7 @@ __export(dist_exports, {
18771
20038
  MUSE_ENGINE_DEFAULT_MODEL: () => MUSE_ENGINE_DEFAULT_MODEL,
18772
20039
  MUSE_ENGINE_MODELS: () => MUSE_ENGINE_MODELS,
18773
20040
  NOREPLY_EMAIL: () => NOREPLY_EMAIL,
20041
+ OPENCODE_GO_DEEPSEEK_GLM_MODELS: () => OPENCODE_GO_DEEPSEEK_GLM_MODELS,
18774
20042
  OPENCODE_GO_MODEL_PATTERN: () => OPENCODE_GO_MODEL_PATTERN,
18775
20043
  OPENCODE_GO_PREFIX: () => OPENCODE_GO_PREFIX,
18776
20044
  OPENROUTER_MAX_CALL_COST_USD: () => OPENROUTER_MAX_CALL_COST_USD,
@@ -18790,10 +20058,12 @@ __export(dist_exports, {
18790
20058
  PLAN_FEATURES: () => PLAN_FEATURES,
18791
20059
  PLAN_FEATURE_LOCKED_CODE: () => PLAN_FEATURE_LOCKED_CODE,
18792
20060
  PLAN_ORDER: () => PLAN_ORDER,
20061
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR: () => PLATFORM_ADMIN_CANCELLED_RUN_ERROR,
18793
20062
  PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
18794
20063
  PLATFORM_DEFAULT_PROVIDER: () => PLATFORM_DEFAULT_PROVIDER,
18795
20064
  PLATFORM_PLAN_BILLING: () => PLATFORM_PLAN_BILLING,
18796
20065
  PLATFORM_SERVER_APPLICATION_NAME: () => PLATFORM_SERVER_APPLICATION_NAME,
20066
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR: () => PLATFORM_SUPPORT_CANCELLED_RUN_ERROR,
18797
20067
  POST_SPECIAL_TRIAL_DAYS: () => POST_SPECIAL_TRIAL_DAYS,
18798
20068
  PRESET_DIRECT_FALLBACK: () => PRESET_DIRECT_FALLBACK,
18799
20069
  PRODUCTION_READ_ONLY_GUARD_TEMPLATE: () => PRODUCTION_READ_ONLY_GUARD_TEMPLATE,
@@ -18805,17 +20075,25 @@ __export(dist_exports, {
18805
20075
  PROJECT_OVERRIDE_FIELDS: () => PROJECT_OVERRIDE_FIELDS,
18806
20076
  PROMOTION_GATE_MAX_CODE_CHARS: () => PROMOTION_GATE_MAX_CODE_CHARS,
18807
20077
  PROMOTION_QUALITY_CHECKS: () => PROMOTION_QUALITY_CHECKS,
20078
+ PROVIDER_ACCOUNT_ID_PATTERN: () => PROVIDER_ACCOUNT_ID_PATTERN,
20079
+ PROVIDER_ACCOUNT_MODEL_PATTERN: () => PROVIDER_ACCOUNT_MODEL_PATTERN,
18808
20080
  PROVIDER_CHAINS: () => PROVIDER_CHAINS,
20081
+ PROVIDER_CHAINS_SETTING_MAX_CHARS: () => PROVIDER_CHAINS_SETTING_MAX_CHARS,
20082
+ PROVIDER_CHAIN_LEADS: () => PROVIDER_CHAIN_LEADS,
20083
+ PROVIDER_CHAIN_MAX_ENTRIES: () => PROVIDER_CHAIN_MAX_ENTRIES,
20084
+ PROVIDER_HOST_NOT_OFFERED: () => PROVIDER_HOST_NOT_OFFERED,
18809
20085
  PUBLIC_PLANS: () => PUBLIC_PLANS,
18810
20086
  QUARANTINE_CLOSE_TAGS: () => QUARANTINE_CLOSE_TAGS,
18811
20087
  READINESS_POLL_MAX_CODE_CHARS: () => READINESS_POLL_MAX_CODE_CHARS,
18812
20088
  READ_ONLY_TAGS: () => READ_ONLY_TAGS,
18813
20089
  RECHECKED_NOTES: () => RECHECKED_NOTES,
18814
20090
  RECORDING_VALUE_PLACEHOLDERS: () => RECORDING_VALUE_PLACEHOLDERS,
20091
+ RENDER_IDENTITY_MAX_LENGTH: () => RENDER_IDENTITY_MAX_LENGTH,
18815
20092
  REPOSITORY_CLIS: () => REPOSITORY_CLIS,
18816
20093
  RESERVED_PROJECT_KEYS: () => RESERVED_PROJECT_KEYS,
18817
20094
  RESULT_STRUCTURE_TOO_COSTLY_CODE: () => RESULT_STRUCTURE_TOO_COSTLY_CODE,
18818
20095
  RETRIED_ATTEMPTS_KEY: () => RETRIED_ATTEMPTS_KEY,
20096
+ RETRY_HELD_RUN_ERROR: () => RETRY_HELD_RUN_ERROR,
18819
20097
  RUNNER_INTERRUPT_ERROR_PREFIX: () => RUNNER_INTERRUPT_ERROR_PREFIX,
18820
20098
  RUN_INTERRUPTED_ERROR_PREFIXES: () => RUN_INTERRUPTED_ERROR_PREFIXES,
18821
20099
  RUN_INTERRUPTION_NOT_RETRIED_MARKERS: () => RUN_INTERRUPTION_NOT_RETRIED_MARKERS,
@@ -18828,6 +20106,8 @@ __export(dist_exports, {
18828
20106
  SHARED_SERVER_APPLICATION_NAME: () => SHARED_SERVER_APPLICATION_NAME,
18829
20107
  SPEND_SOURCE_KEY: () => SPEND_SOURCE_KEY,
18830
20108
  SPEND_SOURCE_SERVER: () => SPEND_SOURCE_SERVER,
20109
+ STATED_LIMITS_VERSION: () => STATED_LIMITS_VERSION,
20110
+ STATED_PATTERN_MAX: () => STATED_PATTERN_MAX,
18831
20111
  STATIC_STANDARD_MIN_INTERACTIVE: () => STATIC_STANDARD_MIN_INTERACTIVE,
18832
20112
  STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
18833
20113
  SUCCESS_NOTICE_TEXT: () => SUCCESS_NOTICE_TEXT,
@@ -18844,6 +20124,7 @@ __export(dist_exports, {
18844
20124
  SURVEY_NOTES_RENDER_MAX_CHARS: () => SURVEY_NOTES_RENDER_MAX_CHARS,
18845
20125
  SURVEY_NOTE_KEYS: () => SURVEY_NOTE_KEYS,
18846
20126
  SUSPICIOUS_PASS_TAG: () => SUSPICIOUS_PASS_TAG,
20127
+ TEST_DID_NOT_RUN_ERROR: () => TEST_DID_NOT_RUN_ERROR,
18847
20128
  TEST_QUALITY_CONVENTIONS: () => TEST_QUALITY_CONVENTIONS,
18848
20129
  TEST_USER_ENV_KEYS: () => TEST_USER_ENV_KEYS,
18849
20130
  TEST_VARIABLE_SECRET_KEY_PATTERN: () => TEST_VARIABLE_SECRET_KEY_PATTERN,
@@ -18856,23 +20137,33 @@ __export(dist_exports, {
18856
20137
  UI_VARIANTS: () => UI_VARIANTS,
18857
20138
  UNEXPECTED_PASS_ERROR_PREFIX: () => UNEXPECTED_PASS_ERROR_PREFIX,
18858
20139
  USER_CANCELLED_RUN_ERROR: () => USER_CANCELLED_RUN_ERROR,
20140
+ VISUAL_MASK_SELECTORS_MAX: () => VISUAL_MASK_SELECTORS_MAX,
20141
+ VISUAL_MASK_SELECTOR_FORBIDDEN: () => VISUAL_MASK_SELECTOR_FORBIDDEN,
20142
+ VISUAL_MASK_SELECTOR_MAX_LENGTH: () => VISUAL_MASK_SELECTOR_MAX_LENGTH,
20143
+ VISUAL_V1_IDENTITY: () => VISUAL_V1_IDENTITY,
18859
20144
  VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
18860
20145
  WEB_PROXY_CHAIN_HEADER: () => WEB_PROXY_CHAIN_HEADER,
18861
20146
  WITHHELD_CREDENTIAL_TEXT: () => WITHHELD_CREDENTIAL_TEXT,
20147
+ ZAI_MODEL_PATTERN: () => ZAI_MODEL_PATTERN,
20148
+ ZAI_PREFIX: () => ZAI_PREFIX,
20149
+ accountServesModel: () => accountServesModel,
18862
20150
  accumulateStatements: () => accumulateStatements,
18863
20151
  adoptBrokerFailover: () => adoptBrokerFailover,
18864
20152
  aiCostToCreditCents: () => aiCostToCreditCents,
20153
+ allowedProviderChainHosts: () => allowedProviderChainHosts,
18865
20154
  alternatesAtTop: () => alternatesAtTop,
18866
20155
  analyzeDerivedValues: () => analyzeDerivedValues,
18867
20156
  analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
18868
20157
  analyzePersistenceReread: () => analyzePersistenceReread,
18869
20158
  analyzeReadinessPolling: () => analyzeReadinessPolling,
20159
+ analyzeRegexMatchers: () => analyzeRegexMatchers,
18870
20160
  analyzeSharedAccountUse: () => analyzeSharedAccountUse,
18871
20161
  analyzeStaleAfterSubmit: () => analyzeStaleAfterSubmit,
18872
20162
  analyzeTestData: () => analyzeTestData,
18873
20163
  analyzeUnreachedUrlWaits: () => analyzeUnreachedUrlWaits,
18874
20164
  analyzeUrlWaits: () => analyzeUrlWaits,
18875
20165
  analyzeWriteResponse: () => analyzeWriteResponse,
20166
+ androidResourceOwners: () => androidResourceOwners,
18876
20167
  apiModelCardsFromConfig: () => apiModelCardsFromConfig,
18877
20168
  apiModelsForProvider: () => apiModelsForProvider,
18878
20169
  apiProviderModelLabel: () => apiProviderModelLabel,
@@ -18883,6 +20174,7 @@ __export(dist_exports, {
18883
20174
  applyLaunchSpecialPlan: () => applyLaunchSpecialPlan,
18884
20175
  asPlainExpect: () => asPlainExpect,
18885
20176
  assertionOnlyCode: () => assertionOnlyCode,
20177
+ assertionStrengthHoistedReadsOn: () => assertionStrengthHoistedReadsOn,
18886
20178
  assertionsReadVisibleContent: () => assertionsReadVisibleContent,
18887
20179
  assessCodeOutcomeCoverage: () => assessCodeOutcomeCoverage,
18888
20180
  assessTitleCoverage: () => assessTitleCoverage,
@@ -18966,6 +20258,7 @@ __export(dist_exports, {
18966
20258
  deepgramSpeak: () => deepgramSpeak,
18967
20259
  deepgramTranscribe: () => deepgramTranscribe,
18968
20260
  defaultConfirmModel: () => defaultConfirmModel,
20261
+ defaultProviderChainEntries: () => defaultProviderChainEntries,
18969
20262
  defaultScreenModel: () => defaultScreenModel,
18970
20263
  defaultVoiceForTtsModel: () => defaultVoiceForTtsModel,
18971
20264
  deriveAuthPlan: () => deriveAuthPlan,
@@ -19008,6 +20301,7 @@ __export(dist_exports, {
19008
20301
  extractOutcomeSearchTerms: () => extractOutcomeSearchTerms,
19009
20302
  featureForAiCallType: () => featureForAiCallType,
19010
20303
  featureForMcpTool: () => featureForMcpTool,
20304
+ featureRowRunStatus: () => featureRowRunStatus,
19011
20305
  findBracedClause: () => findBracedClause,
19012
20306
  findCounterIdPins: () => findCounterIdPins,
19013
20307
  findCredentialTextLeftover: () => findCredentialTextLeftover,
@@ -19030,6 +20324,7 @@ __export(dist_exports, {
19030
20324
  formatPersistenceRereadAdvisory: () => formatPersistenceRereadAdvisory,
19031
20325
  formatPoolBudgetLine: () => formatPoolBudgetLine,
19032
20326
  formatReadinessPollAdvisory: () => formatReadinessPollAdvisory,
20327
+ formatRegexMatcherNote: () => formatRegexMatcherNote,
19033
20328
  formatStaleAfterSubmitAdvisory: () => formatStaleAfterSubmitAdvisory,
19034
20329
  formatSuspiciousExpectation: () => formatSuspiciousExpectation,
19035
20330
  formatUrlWaitAdvisory: () => formatUrlWaitAdvisory,
@@ -19055,6 +20350,7 @@ __export(dist_exports, {
19055
20350
  getPlanChangeType: () => getPlanChangeType,
19056
20351
  getPlanDefinition: () => getPlanDefinition,
19057
20352
  getPlanLabel: () => getPlanLabel,
20353
+ getProviderChainOverrides: () => getProviderChainOverrides,
19058
20354
  getTrialDaysRemaining: () => getTrialDaysRemaining,
19059
20355
  googleBrokerNavigationUrl: () => googleBrokerNavigationUrl,
19060
20356
  googleBrokerUnavailableLine: () => googleBrokerUnavailableLine,
@@ -19071,9 +20367,11 @@ __export(dist_exports, {
19071
20367
  hasOpenCodeGoPrefix: () => hasOpenCodeGoPrefix,
19072
20368
  hasOpenRouterPrefix: () => hasOpenRouterPrefix,
19073
20369
  hasPlatformLocator: () => hasPlatformLocator,
20370
+ hasProviderChainLeadsByDefault: () => hasProviderChainLeadsByDefault,
19074
20371
  hasReadOnlyMarker: () => hasReadOnlyMarker,
19075
20372
  hasSurveyNotes: () => hasSurveyNotes,
19076
20373
  hasTestFailContract: () => hasTestFailContract,
20374
+ hasZaiPrefix: () => hasZaiPrefix,
19077
20375
  healMobileTestSteps: () => healMobileTestSteps,
19078
20376
  healthRunWhere: () => healthRunWhere,
19079
20377
  heldCodeRecord: () => heldCodeRecord,
@@ -19095,8 +20393,11 @@ __export(dist_exports, {
19095
20393
  isAuthTokenName: () => isAuthTokenName,
19096
20394
  isBlockedEnvVar: () => isBlockedEnvVar,
19097
20395
  isBrowserCliEngine: () => isBrowserCliEngine,
20396
+ isBrowserVersion: () => isBrowserVersion,
19098
20397
  isBugTestBrokenError: () => isBugTestBrokenError,
20398
+ isCancelledReason: () => isCancelledReason,
19099
20399
  isCanvasUnsupportedMessage: () => isCanvasUnsupportedMessage,
20400
+ isChainablePrimary: () => isChainablePrimary,
19100
20401
  isCliEngineModelId: () => isCliEngineModelId,
19101
20402
  isCliEngineSpec: () => isCliEngineSpec,
19102
20403
  isCodeFactNote: () => isCodeFactNote,
@@ -19120,6 +20421,7 @@ __export(dist_exports, {
19120
20421
  isIdentityMobileXpath: () => isIdentityMobileXpath,
19121
20422
  isIdentityProviderSurface: () => isIdentityProviderSurface,
19122
20423
  isIncidentalAssetPath: () => isIncidentalAssetPath,
20424
+ isIntentionalNegativeAuthTest: () => isIntentionalNegativeAuthTest,
19123
20425
  isIpOrLocalhost: () => isIpOrLocalhost,
19124
20426
  isKnownPageType: () => isKnownPageType,
19125
20427
  isLaunchSpecialActive: () => isLaunchSpecialActive,
@@ -19147,6 +20449,7 @@ __export(dist_exports, {
19147
20449
  isRunInterruptedError: () => isRunInterruptedError,
19148
20450
  isRunnableStep: () => isRunnableStep,
19149
20451
  isSafeDeepLink: () => isSafeDeepLink,
20452
+ isSafeMaskSelector: () => isSafeMaskSelector,
19150
20453
  isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
19151
20454
  isSelectableApiModel: () => isSelectableApiModel,
19152
20455
  isSelfServePlan: () => isSelfServePlan,
@@ -19177,6 +20480,7 @@ __export(dist_exports, {
19177
20480
  isValidSttProvider: () => isValidSttProvider,
19178
20481
  isValidTtsModel: () => isValidTtsModel,
19179
20482
  isValidTtsVoiceId: () => isValidTtsVoiceId,
20483
+ isZaiModelId: () => isZaiModelId,
19180
20484
  jsEscapedForms: () => jsEscapedForms,
19181
20485
  jsonStructureRefusalOf: () => jsonStructureRefusalOf,
19182
20486
  jsonStructureVerify: () => jsonStructureVerify,
@@ -19209,6 +20513,7 @@ __export(dist_exports, {
19209
20513
  normalizeFinishReason: () => normalizeFinishReason,
19210
20514
  normalizeNoteText: () => normalizeNoteText,
19211
20515
  normalizePageType: () => normalizePageType,
20516
+ normalizeProviderAccountModels: () => normalizeProviderAccountModels,
19212
20517
  normalizeTtsModel: () => normalizeTtsModel,
19213
20518
  normalizeTtsVoiceId: () => normalizeTtsVoiceId,
19214
20519
  noteChatFailover: () => noteChatFailover,
@@ -19225,6 +20530,7 @@ __export(dist_exports, {
19225
20530
  parseApiCardModelUpdate: () => parseApiCardModelUpdate,
19226
20531
  parseAppTimezone: () => parseAppTimezone,
19227
20532
  parseAuditGroupScopeId: () => parseAuditGroupScopeId,
20533
+ parseAuthSessionStamp: () => parseAuthSessionStamp,
19228
20534
  parseBoolFlag: () => parseBoolFlag,
19229
20535
  parseDbPoolSize: () => parseDbPoolSize,
19230
20536
  parseDeepgramListenResponse: () => parseDeepgramListenResponse,
@@ -19232,13 +20538,18 @@ __export(dist_exports, {
19232
20538
  parseGeminiModelsPayload: () => parseGeminiModelsPayload,
19233
20539
  parseGoogleAuthorizeUrl: () => parseGoogleAuthorizeUrl,
19234
20540
  parseGoogleGisRequestUrl: () => parseGoogleGisRequestUrl,
20541
+ parseLastActionProofMode: () => parseLastActionProofMode,
19235
20542
  parseLimitParam: () => parseLimitParam,
19236
20543
  parseOffsetParam: () => parseOffsetParam,
19237
20544
  parsePageParam: () => parsePageParam,
19238
20545
  parsePgIntSetting: () => parsePgIntSetting,
19239
20546
  parseProjectEngineOverrides: () => parseProjectEngineOverrides,
19240
20547
  parsePromotionGateMode: () => parsePromotionGateMode,
20548
+ parseProviderAccountLists: () => parseProviderAccountLists,
20549
+ parseProviderChainOverrides: () => parseProviderChainOverrides,
19241
20550
  parseRecheckClearMode: () => parseRecheckClearMode,
20551
+ parseRenderIdentity: () => parseRenderIdentity,
20552
+ parseStoredProviderChainOverrides: () => parseStoredProviderChainOverrides,
19242
20553
  parseSurveyEntryUrls: () => parseSurveyEntryUrls,
19243
20554
  parseSuspiciousPassMode: () => parseSuspiciousPassMode,
19244
20555
  parseTaskModelUpdate: () => parseTaskModelUpdate,
@@ -19268,13 +20579,20 @@ __export(dist_exports, {
19268
20579
  profileKeyRoute: () => profileKeyRoute,
19269
20580
  projectAppHosts: () => projectAppHosts,
19270
20581
  providerChainFor: () => providerChainFor,
20582
+ providerChainHostOf: () => providerChainHostOf,
20583
+ providerChainHostProvider: () => providerChainHostProvider,
20584
+ providerChainLeadsFor: () => providerChainLeadsFor,
20585
+ providerChainModelKey: () => providerChainModelKey,
19271
20586
  providerChainPrimariesForHop: () => providerChainPrimariesForHop,
20587
+ providerHostPlan: () => providerHostPlan,
20588
+ readBalanced: () => readBalanced,
19272
20589
  readChatFailover: () => readChatFailover,
19273
20590
  readChatReasoningTokens: () => readChatReasoningTokens,
19274
20591
  readChatUsage: () => readChatUsage,
19275
20592
  readEnabledEngines: () => readEnabledEngines,
19276
20593
  readProjectEngineOverrides: () => readProjectEngineOverrides,
19277
20594
  readRetriedAttempts: () => readRetriedAttempts,
20595
+ readStatedLimits: () => readStatedLimits,
19278
20596
  readStaticPageCanvasSignals: () => readStaticPageCanvasSignals,
19279
20597
  readSurveyEntryUrls: () => readSurveyEntryUrls,
19280
20598
  readSurveyNotes: () => readSurveyNotes,
@@ -19287,6 +20605,7 @@ __export(dist_exports, {
19287
20605
  regexLiteralEnd: () => regexLiteralEnd2,
19288
20606
  regexOpensAfter: () => regexOpensAfter,
19289
20607
  registrableDomain: () => registrableDomain,
20608
+ renderIdentity: () => renderIdentity,
19290
20609
  renderSurveyNotesForPrompt: () => renderSurveyNotesForPrompt,
19291
20610
  repairPageType: () => repairPageType,
19292
20611
  replaceBlockComments: () => replaceBlockComments,
@@ -19334,11 +20653,14 @@ __export(dist_exports, {
19334
20653
  selectorCandidatesForStep: () => selectorCandidatesForStep,
19335
20654
  selectorForValue: () => selectorForValue,
19336
20655
  setAgenticCliRuntime: () => setAgenticCliRuntime,
20656
+ setProviderChainOverrides: () => setProviderChainOverrides,
19337
20657
  signupPlanFor: () => signupPlanFor,
19338
20658
  simplifyMobileWanderingInPlace: () => simplifyMobileWanderingInPlace,
19339
20659
  siteOf: () => siteOf,
19340
20660
  specItemsFromTestPlans: () => specItemsFromTestPlans,
19341
20661
  splitAnswerLines: () => splitAnswerLines,
20662
+ statedLimitsFromAttrs: () => statedLimitsFromAttrs,
20663
+ statedLimitsRecord: () => statedLimitsRecord,
19342
20664
  stepAppliesToPlatform: () => stepAppliesToPlatform,
19343
20665
  stepConsoleLines: () => stepConsoleLines,
19344
20666
  stepIsDivergentForPlatform: () => stepIsDivergentForPlatform,
@@ -19350,7 +20672,11 @@ __export(dist_exports, {
19350
20672
  stripCodeFactPrefix: () => stripCodeFactPrefix,
19351
20673
  stripCoverageComments: () => stripCoverageComments,
19352
20674
  stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
20675
+ stripFractionZeros: () => stripFractionZeros,
20676
+ stripLineComment: () => stripLineComment,
19353
20677
  stripLoginScaffolding: () => stripLoginScaffolding,
20678
+ stripTrailingChars: () => stripTrailingChars,
20679
+ stripTrailingSlashes: () => stripTrailingSlashes,
19354
20680
  strongerNeedsReviewSource: () => strongerNeedsReviewSource,
19355
20681
  sttModelsForProvider: () => sttModelsForProvider,
19356
20682
  summarizeHealth: () => summarizeHealth,
@@ -19367,10 +20693,14 @@ __export(dist_exports, {
19367
20693
  ttsFallbackModel: () => ttsFallbackModel,
19368
20694
  ttsProviderForModel: () => ttsProviderForModel,
19369
20695
  unexpectedPassError: () => unexpectedPassError,
20696
+ v1RenderIdentity: () => v1RenderIdentity,
19370
20697
  validateAppMapDocument: () => validateAppMapDocument,
19371
20698
  validateAssertionStrength: () => validateAssertionStrength,
19372
20699
  validateEngineSpec: () => validateEngineSpec,
20700
+ validateProviderChainEntries: () => validateProviderChainEntries,
20701
+ validateProviderChainsSetting: () => validateProviderChainsSetting,
19373
20702
  validateTaskModelId: () => validateTaskModelId,
20703
+ visualBaselinesEnabled: () => visualBaselinesEnabled,
19374
20704
  visualProDailyBudget: () => visualProDailyBudget,
19375
20705
  voiceMatchesTtsModel: () => voiceMatchesTtsModel,
19376
20706
  voicesForTtsModel: () => voicesForTtsModel,
@@ -19378,7 +20708,8 @@ __export(dist_exports, {
19378
20708
  withReviewSource: () => withReviewSource,
19379
20709
  withoutNotChargedClaim: () => withoutNotChargedClaim,
19380
20710
  withoutServerOnlySpendFields: () => withoutServerOnlySpendFields,
19381
- withoutSurveyNotes: () => withoutSurveyNotes
20711
+ withoutSurveyNotes: () => withoutSurveyNotes,
20712
+ zaiWireModel: () => zaiWireModel
19382
20713
  });
19383
20714
  var init_dist = __esm({
19384
20715
  "../shared/dist/index.js"() {
@@ -19420,6 +20751,7 @@ var init_dist = __esm({
19420
20751
  init_admin_view_return_to();
19421
20752
  init_auth_plan();
19422
20753
  init_auth_block_detector();
20754
+ init_auth_session_stamp();
19423
20755
  init_auth_state_freshness();
19424
20756
  init_skip_coverage_detector();
19425
20757
  init_feature_coverage();
@@ -19431,11 +20763,14 @@ var init_dist = __esm({
19431
20763
  init_deepgram_voices();
19432
20764
  init_feature_flags();
19433
20765
  init_env_flags();
20766
+ init_visual_compare_limits();
20767
+ init_visual_baseline_identity();
19434
20768
  init_analytics_url();
19435
20769
  init_agentic_cli_runtime();
19436
20770
  init_api_model_catalog();
19437
20771
  init_engine_spec();
19438
20772
  init_provider_chains();
20773
+ init_provider_accounts();
19439
20774
  init_project_engine_overrides();
19440
20775
  init_ai_profiles();
19441
20776
  init_openrouter_usage();
@@ -19476,6 +20811,8 @@ var init_dist = __esm({
19476
20811
  init_promotion_quality_gate();
19477
20812
  init_regex_alternation();
19478
20813
  init_page_evidence();
20814
+ init_trailing_slashes();
20815
+ init_stated_limits();
19479
20816
  }
19480
20817
  });
19481
20818
 
@@ -19614,7 +20951,7 @@ var require_repo_brief = __commonJS({
19614
20951
  const signupJobs = brief.jobs.filter((job) => /sign.?up|register|create account/i.test(`${job.name} ${job.entry}`));
19615
20952
  return signupJobs.some((job) => job.validationRules.some(rejectsPlus)) || (brief.applicationFacts ?? []).some((fact) => /sign.?up|register|create account/i.test(fact.area) && rejectsPlus(`${fact.behavior} ${fact.testImplication}`));
19616
20953
  }
19617
- var KINDS = /* @__PURE__ */ new Set(["happy_path", "validation", "permission", "edge_case", "bug_risk"]);
20954
+ var KINDS2 = /* @__PURE__ */ new Set(["happy_path", "validation", "permission", "edge_case", "bug_risk"]);
19618
20955
  var SESSIONS = /* @__PURE__ */ new Set(["none", "optional", "required"]);
19619
20956
  var FACT_KINDS = /* @__PURE__ */ new Set(["workflow", "business_rule", "state_transition", "permission", "integration", "data_model"]);
19620
20957
  var MISSING_REASON = "cited by the reader without a reason";
@@ -19727,7 +21064,7 @@ var require_repo_brief = __commonJS({
19727
21064
  const scenarioRefs = refs(scenario.sourceRefs);
19728
21065
  if (typeof scenarioName !== "string" || typeof expected !== "string" || scenarioRefs.length === 0)
19729
21066
  continue;
19730
- if (typeof kind !== "string" || !KINDS.has(kind))
21067
+ if (typeof kind !== "string" || !KINDS2.has(kind))
19731
21068
  continue;
19732
21069
  scenarios.push({
19733
21070
  name: scenarioName.trim().slice(0, 160),
@@ -19911,6 +21248,7 @@ var require_route_manifest = __commonJS({
19911
21248
  exports2.renderRouteManifestChecklist = renderRouteManifestChecklist;
19912
21249
  exports2.renderRepoReadsChecklist = renderRepoReadsChecklist;
19913
21250
  exports2.buildRepoReadsRouteAllowlist = buildRepoReadsRouteAllowlist;
21251
+ var shared_1 = (init_dist(), __toCommonJS(dist_exports));
19914
21252
  var promises_1 = require("fs/promises");
19915
21253
  var node_path_1 = require("path");
19916
21254
  var untrusted_knowledge_js_1 = require_untrusted_knowledge();
@@ -19926,7 +21264,7 @@ var require_route_manifest = __commonJS({
19926
21264
  return `routes-${n}.json`;
19927
21265
  }
19928
21266
  var AUTH_HINTS = /* @__PURE__ */ new Set(["public", "auth", "unknown"]);
19929
- var KINDS = /* @__PURE__ */ new Set(["page", "api", "redirect"]);
21267
+ var KINDS2 = /* @__PURE__ */ new Set(["page", "api", "redirect"]);
19930
21268
  var COMPLETENESS = /* @__PURE__ */ new Set(["complete", "partial", "unsupported"]);
19931
21269
  var PROVENANCES = /* @__PURE__ */ new Set(["cli", "file-convention", "route-config"]);
19932
21270
  function str(value, max) {
@@ -19965,7 +21303,7 @@ var require_route_manifest = __commonJS({
19965
21303
  }
19966
21304
  function collapsePath(path) {
19967
21305
  const collapsed = path.replace(/\/{2,}/g, "/");
19968
- return collapsed.length > 1 ? collapsed.replace(/\/+$/, "") : collapsed;
21306
+ return collapsed.length > 1 ? (0, shared_1.stripTrailingSlashes)(collapsed) : collapsed;
19969
21307
  }
19970
21308
  function normalizeManifestPath(raw) {
19971
21309
  const text = str(raw, 500);
@@ -20033,7 +21371,7 @@ var require_route_manifest = __commonJS({
20033
21371
  if (seenFamilies.has(family)) {
20034
21372
  const kept = routes.find((r2) => manifestRouteFamilyKey(r2.concretePath ?? r2.pattern ?? "") === family);
20035
21373
  const more = strList(rr.methods, 8, 10).map((m) => m.toUpperCase()).filter((m) => /^[A-Z]{3,10}$/.test(m));
20036
- const rowKind = KINDS.has(rr.kind) ? rr.kind : normalized.startsWith("/api/") ? "api" : "page";
21374
+ const rowKind = KINDS2.has(rr.kind) ? rr.kind : normalized.startsWith("/api/") ? "api" : "page";
20037
21375
  if (kept?.kind === "api" && rowKind === "api" && more.length) {
20038
21376
  kept.methods = [.../* @__PURE__ */ new Set([...kept.methods ?? [], ...more])].slice(0, 8);
20039
21377
  } else {
@@ -20047,7 +21385,7 @@ var require_route_manifest = __commonJS({
20047
21385
  }
20048
21386
  seenFamilies.add(family);
20049
21387
  index += 1;
20050
- const kind = KINDS.has(rr.kind) ? rr.kind : normalized.startsWith("/api/") ? "api" : "page";
21388
+ const kind = KINDS2.has(rr.kind) ? rr.kind : normalized.startsWith("/api/") ? "api" : "page";
20051
21389
  const authHint = AUTH_HINTS.has(rr.authHint) ? rr.authHint : "unknown";
20052
21390
  const methods = strList(rr.methods, 8, 10).map((m) => m.toUpperCase()).filter((m) => /^[A-Z]{3,10}$/.test(m));
20053
21391
  const sourceCheck = rr.sourceCheck === "matched" || rr.sourceCheck === "unmatched" ? rr.sourceCheck : void 0;
@@ -20303,6 +21641,7 @@ var require_exploration_parser = __commonJS({
20303
21641
  Object.defineProperty(exports2, "__esModule", { value: true });
20304
21642
  exports2.extractCodeLines = extractCodeLines;
20305
21643
  exports2.extractProvenLocator = extractProvenLocator;
21644
+ exports2.maskTypedValues = maskTypedValues;
20306
21645
  exports2.sanitizePromptCodeLine = sanitizePromptCodeLine;
20307
21646
  exports2.extractPageUrl = extractPageUrl;
20308
21647
  exports2.extractPageTitle = extractPageTitle;
@@ -20314,9 +21653,10 @@ var require_exploration_parser = __commonJS({
20314
21653
  exports2.normalizeRouteForFocusScope = normalizeRouteForFocusScope;
20315
21654
  exports2.routeIsWithinFocusAreas = routeIsWithinFocusAreas;
20316
21655
  exports2.detectRouteFromResponse = detectRouteFromResponse;
21656
+ var shared_1 = (init_dist(), __toCommonJS(dist_exports));
20317
21657
  function extractCodeLines(toolResultContent) {
20318
21658
  const isPlaywrightLine = (l) => l.startsWith("await page.") || l.startsWith("await expect(");
20319
- const codeSection = toolResultContent.match(/###\s*Ran Playwright code\s*\n([\s\S]*?)(?=\n###|\n$|$)/);
21659
+ const codeSection = toolResultContent.match(/###\s*Ran Playwright code\s*\n(?![^\S\n]*\n)([\s\S]*?)(?=\n###|\n$|$)/);
20320
21660
  if (codeSection) {
20321
21661
  return codeSection[1].split("\n").map((l) => l.trim()).filter(isPlaywrightLine).map((l) => l.replace(/;$/, ""));
20322
21662
  }
@@ -20360,7 +21700,7 @@ var require_exploration_parser = __commonJS({
20360
21700
  cut = idx;
20361
21701
  }
20362
21702
  let locator = cut > start ? codeLine.slice(start, cut) : codeLine.slice(start);
20363
- locator = locator.trim().replace(/;+\s*$/, "").trim();
21703
+ locator = locator.trim().replace(/(?<!;);+\s*$/, "").trim();
20364
21704
  if (!locator.includes("("))
20365
21705
  return null;
20366
21706
  if (!/^(getBy\w+|locator)\(/.test(locator))
@@ -20440,13 +21780,16 @@ var require_exploration_parser = __commonJS({
20440
21780
  return false;
20441
21781
  return RECORD_TITLE_WITH_COMPANY.test(name);
20442
21782
  }
21783
+ var TYPED_VALUE_CALL_RE = /\.(fill|type|pressSequentially|insertText)\(\s*(['"])(?:\\.|(?!\2)[^\\])*\2(\s*,\s*\{[^{}]*\})?\s*\)/g;
21784
+ function maskTypedValues(codeLine, mask) {
21785
+ return codeLine.replace(TYPED_VALUE_CALL_RE, (_all, method, _quote, options) => `.${method}(${mask}${options ?? ""})`);
21786
+ }
20443
21787
  function sanitizePromptCodeLine(codeLine) {
20444
21788
  let sanitized = codeLine;
20445
21789
  sanitized = sanitized.replace(/getByRole\('(textbox|searchbox|combobox|spinbutton)',\s*\{\s*name:\s*['"]([^'"]*)['"]\s*\}\)/g, (match, role, name, _offset, line) => looksDynamicFieldLabel(name, line) ? `getByRole('${role}')` : match);
20446
21790
  sanitized = sanitized.replace(/getByLabel\(\s*(['"])([^'"]*)\1\s*\)(?=\)?\.(?:fill|type|toHaveValue|inputValue|press)\()/g, (match, _quote, label, _offset, line) => looksDynamicFieldLabel(label, line) ? "getByRole('textbox')" : match);
20447
21791
  sanitized = sanitized.replace(/getByLabel\(\s*(['"])([^'"]*)\1\s*\)(?=\)?\.selectOption\()/g, (match, _quote, label, _offset, line) => looksDynamicFieldLabel(label, line) ? "getByRole('combobox')" : match);
20448
- sanitized = sanitized.replace(/\.fill\(\s*(['"]).*?\1\s*\)/g, ".fill('<test-value>')");
20449
- sanitized = sanitized.replace(/\.type\(\s*(['"]).*?\1\s*\)/g, ".type('<test-value>')");
21792
+ sanitized = maskTypedValues(sanitized, "'<test-value>'");
20450
21793
  sanitized = sanitized.replace(/\.toHaveValue\(\s*(['"]).*?\1\s*\)/g, ".toHaveValue(/./)");
20451
21794
  sanitized = sanitized.replace(/getByRole\('heading',\s*\{\s*name:\s*'([^']*)'\s*\}\)/g, (match, name) => looksDynamicRecordTitle(name) ? "getByRole('heading')" : match);
20452
21795
  sanitized = sanitized.replace(/getByRole\('heading',\s*\{\s*name:\s*"([^"]*)"\s*\}\)/g, (match, name) => looksDynamicRecordTitle(name) ? "getByRole('heading')" : match);
@@ -20470,7 +21813,7 @@ var require_exploration_parser = __commonJS({
20470
21813
  return match ? match[1].trim() : null;
20471
21814
  }
20472
21815
  function extractSnapshotSection(content) {
20473
- const match = content.match(/###\s*Snapshot\s*\n([\s\S]*?)(?=\n###|$)/);
21816
+ const match = content.match(/###\s*Snapshot\s*\n(?![^\S\n]*\n)([\s\S]*?)(?=\n###|$)/);
20474
21817
  if (!match)
20475
21818
  return null;
20476
21819
  if (/^\s*- \[Snapshot\]\([^)\n]*\)\s*$/.test(match[1]))
@@ -20538,7 +21881,7 @@ var require_exploration_parser = __commonJS({
20538
21881
  return hints;
20539
21882
  }
20540
21883
  function extractFirstAlert(snapshotYaml) {
20541
- const match = snapshotYaml.match(/^\s*-\s+(?:alert|status)\s+"([^"]+)"/m);
21884
+ const match = snapshotYaml.match(/^[^\S\n\r\u2028\u2029]*-\s+(?:alert|status)\s+"([^"]+)"/m);
20542
21885
  return match ? match[1] : null;
20543
21886
  }
20544
21887
  function buildInteractionLogEntry(toolName, toolArgs, codeLines, pageUrl, pageTitle, snapshotSection) {
@@ -20579,6 +21922,12 @@ var require_exploration_parser = __commonJS({
20579
21922
  return /^\d{10,}$/.test(value);
20580
21923
  return false;
20581
21924
  }
21925
+ function hashRouterPathWithoutTrailingSlash(hash) {
21926
+ const q = hash.indexOf("?");
21927
+ const path = q < 0 ? hash : hash.slice(0, q);
21928
+ const stripped = (0, shared_1.stripTrailingSlashes)(path);
21929
+ return stripped === "#" || stripped === "" ? hash : stripped + (q < 0 ? "" : hash.slice(q));
21930
+ }
20582
21931
  function normalizeRoute(url) {
20583
21932
  let route = url.pathname;
20584
21933
  const encodeQueryPart = (part) => part.replace(/[%&=#+?]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
@@ -20597,7 +21946,7 @@ var require_exploration_parser = __commonJS({
20597
21946
  }
20598
21947
  }
20599
21948
  if (url.hash && url.hash !== "#" && url.hash !== "#/") {
20600
- route += url.hash;
21949
+ route += url.hash.startsWith("#/") ? hashRouterPathWithoutTrailingSlash(url.hash) : url.hash;
20601
21950
  }
20602
21951
  return route;
20603
21952
  }
@@ -20607,7 +21956,7 @@ var require_exploration_parser = __commonJS({
20607
21956
  const decoded = raw.replace(/&amp;/g, "&").replace(/&#(\d+);/g, (_m, code) => String.fromCharCode(Number(code)));
20608
21957
  try {
20609
21958
  const url = new URL(decoded, "http://canonical.invalid");
20610
- url.pathname = url.pathname.replace(/\/+$/, "") || "/";
21959
+ url.pathname = (0, shared_1.stripTrailingSlashes)(url.pathname) || "/";
20611
21960
  return normalizeRoute(url);
20612
21961
  } catch {
20613
21962
  return decoded;
@@ -20639,9 +21988,9 @@ var require_exploration_parser = __commonJS({
20639
21988
  const text = String(route ?? "").trim();
20640
21989
  const at = text.indexOf("#/");
20641
21990
  if (at < 0)
20642
- return text.startsWith("#") ? text.split("?")[0].toLowerCase().replace(/\/+$/, "") : null;
21991
+ return text.startsWith("#") ? (0, shared_1.stripTrailingSlashes)(text.split("?")[0].toLowerCase()) : null;
20643
21992
  const hashPath = text.slice(at).split("?")[0].toLowerCase();
20644
- return hashPath.length > 2 ? hashPath.replace(/\/+$/, "") || "#/" : hashPath;
21993
+ return hashPath.length > 2 ? (0, shared_1.stripTrailingSlashes)(hashPath) || "#/" : hashPath;
20645
21994
  }
20646
21995
  function routeIsWithinFocusAreas(route, focusAreas, otherRoutes) {
20647
21996
  if (!focusAreas || focusAreas.length === 0)
@@ -20898,8 +22247,12 @@ var require_survey_network_guard = __commonJS({
20898
22247
  exports2.maskedPath = maskedPath;
20899
22248
  exports2.bodyKeysOf = bodyKeysOf;
20900
22249
  exports2.graphqlDocumentIsQueryOnly = graphqlDocumentIsQueryOnly;
22250
+ exports2.graphqlOperationRoots = graphqlOperationRoots;
20901
22251
  exports2.isTokenRefreshWrite = isTokenRefreshWrite;
20902
22252
  exports2.isSignInPathWrite = isSignInPathWrite;
22253
+ exports2.graphqlUrlCarriesQuery = graphqlUrlCarriesQuery;
22254
+ exports2.graphqlUrlQueriesAreReads = graphqlUrlQueriesAreReads;
22255
+ exports2.graphqlGetMayWrite = graphqlGetMayWrite;
20903
22256
  exports2.writeShapedGet = writeShapedGet;
20904
22257
  exports2.decideSurveyRequest = decideSurveyRequest;
20905
22258
  exports2.installSurveyNetworkGuard = installSurveyNetworkGuard;
@@ -21176,6 +22529,100 @@ var require_survey_network_guard = __commonJS({
21176
22529
  }
21177
22530
  return depth === 0 && parens === 0 && objects === 0 && sawOperation;
21178
22531
  }
22532
+ function graphqlOperationRoots(text) {
22533
+ const tokens2 = lexGraphql(text);
22534
+ if (!tokens2)
22535
+ return null;
22536
+ const ops = [];
22537
+ let depth = 0;
22538
+ let parens = 0;
22539
+ let objects = 0;
22540
+ let defStart = true;
22541
+ let kind = null;
22542
+ let current = null;
22543
+ let prev = "";
22544
+ for (const tok of tokens2) {
22545
+ const last = prev;
22546
+ prev = tok;
22547
+ if (tok === "(") {
22548
+ parens++;
22549
+ continue;
22550
+ }
22551
+ if (tok === ")") {
22552
+ parens--;
22553
+ if (parens < 0 || objects !== 0)
22554
+ return null;
22555
+ continue;
22556
+ }
22557
+ if (parens > 0) {
22558
+ if (tok === "{")
22559
+ objects++;
22560
+ else if (tok === "}") {
22561
+ objects--;
22562
+ if (objects < 0)
22563
+ return null;
22564
+ }
22565
+ continue;
22566
+ }
22567
+ if (tok === "{") {
22568
+ if (depth === 0) {
22569
+ if (defStart)
22570
+ kind = "query";
22571
+ else if (kind === null)
22572
+ return null;
22573
+ current = kind === "fragment" ? null : { kind, roots: [] };
22574
+ if (current)
22575
+ ops.push(current);
22576
+ defStart = false;
22577
+ }
22578
+ depth++;
22579
+ continue;
22580
+ }
22581
+ if (tok === "}") {
22582
+ depth--;
22583
+ if (depth < 0)
22584
+ return null;
22585
+ if (depth === 0) {
22586
+ defStart = true;
22587
+ kind = null;
22588
+ current = null;
22589
+ }
22590
+ continue;
22591
+ }
22592
+ if (depth === 0) {
22593
+ if (!/^[_A-Za-z]/.test(tok)) {
22594
+ if (tok === "@")
22595
+ continue;
22596
+ return null;
22597
+ }
22598
+ if (defStart) {
22599
+ if (tok !== "query" && tok !== "mutation" && tok !== "fragment")
22600
+ return null;
22601
+ kind = tok;
22602
+ defStart = false;
22603
+ }
22604
+ continue;
22605
+ }
22606
+ if (depth > 1 || !current)
22607
+ continue;
22608
+ if (/^[_A-Za-z]/.test(tok)) {
22609
+ if (last === "@")
22610
+ continue;
22611
+ if (last === ":") {
22612
+ if (current.roots.length === 0)
22613
+ return null;
22614
+ current.roots[current.roots.length - 1] = tok;
22615
+ continue;
22616
+ }
22617
+ current.roots.push(tok);
22618
+ continue;
22619
+ }
22620
+ if (tok === ":" || tok === "@")
22621
+ continue;
22622
+ return null;
22623
+ }
22624
+ return depth === 0 && parens === 0 && objects === 0 ? ops : null;
22625
+ }
21179
22626
  function isGraphqlQueryOnly(postData) {
21180
22627
  if (!postData || postData.length > MAX_BODY_CHARS)
21181
22628
  return false;
@@ -21213,6 +22660,34 @@ var require_survey_network_guard = __commonJS({
21213
22660
  "remove"
21214
22661
  ]);
21215
22662
  var NOT_A_SESSION_END = (segs) => !segs.some((seg) => SESSION_END_SEGMENTS.has(seg));
22663
+ var DESTRUCTIVE_WORDS = /* @__PURE__ */ new Set([
22664
+ "delete",
22665
+ "remove",
22666
+ "destroy",
22667
+ "deactivate",
22668
+ "terminate",
22669
+ "purge",
22670
+ "wipe",
22671
+ "erase",
22672
+ "cancel",
22673
+ "close",
22674
+ "revoke",
22675
+ "unlink",
22676
+ "disconnect"
22677
+ ]);
22678
+ var DESTRUCTIVE_PREFIXES = ["delete", "remove", "destroy", "deactivate", "terminate", "purge"];
22679
+ function hasDestructiveSegment(rawSegs) {
22680
+ return rawSegs.some((raw) => {
22681
+ let seg = raw;
22682
+ try {
22683
+ seg = decodeURIComponent(raw);
22684
+ } catch {
22685
+ }
22686
+ const words = seg.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/);
22687
+ return words.some((word) => DESTRUCTIVE_WORDS.has(word) || DESTRUCTIVE_PREFIXES.some((p) => word.startsWith(p)));
22688
+ });
22689
+ }
22690
+ var rawPathSegments = (pathname) => pathname.split("/").map((seg) => seg.split(";")[0]).filter(Boolean);
21216
22691
  var pathSegments = (pathname) => pathname.toLowerCase().split("/").map((seg) => seg.split(";")[0]).filter(Boolean);
21217
22692
  function isTokenRefreshWrite(method, pathname, recentAction = false) {
21218
22693
  if (method.toUpperCase() !== "POST")
@@ -21229,6 +22704,8 @@ var require_survey_network_guard = __commonJS({
21229
22704
  var AUTH_PAGE_PATH_RE = /(?:^|\/)(?:log-?in|sign-?in|sign_in|auth|oauth2?|sso|callback)(?:\/|$)/;
21230
22705
  var SIGN_IN_SEGMENT_RE = /^(?:log-?in|sign-?in|sign_in)$/;
21231
22706
  var CALLBACK_ROOT_SEGMENTS = /* @__PURE__ */ new Set(["auth", "oauth", "oauth2", "sso"]);
22707
+ var CALLBACK_PROTOCOL_SEGMENTS = /* @__PURE__ */ new Set(["oidc", "openid", "openid-connect", "openidconnect", "saml", "saml2", "oauth", "oauth2"]);
22708
+ var API_VERSION_SEGMENT_RE = /^v[0-9]{1,3}$/;
21232
22709
  var PROVIDER_SEGMENT_RE = /^[a-z][a-z0-9_-]{0,39}$/;
21233
22710
  var NOT_A_PROVIDER_SEGMENTS = /* @__PURE__ */ new Set([
21234
22711
  "link",
@@ -21267,14 +22744,21 @@ var require_survey_network_guard = __commonJS({
21267
22744
  if (!NOT_A_SESSION_END(segs))
21268
22745
  return false;
21269
22746
  if (AUTH_END_PATH_RE.test(`/${segs.join("/")}`))
21270
- return true;
22747
+ return !hasDestructiveSegment(rawPathSegments(pathname).slice(0, -1));
22748
+ let top = segs[0] === "api" ? 1 : 0;
22749
+ if (top < segs.length && API_VERSION_SEGMENT_RE.test(segs[top]))
22750
+ top += 1;
22751
+ const below = segs.length - top;
22752
+ const isProvider = (seg) => PROVIDER_SEGMENT_RE.test(seg) && !NOT_A_PROVIDER_SEGMENTS.has(seg);
21271
22753
  const provider = segs[segs.length - 1];
21272
- const authRootAtTop = segs.length === 3 || segs[segs.length - 4] === "api" && (segs.length === 4 || segs[segs.length - 3] === "auth");
21273
- if (segs.length >= 3 && authRootAtTop && CALLBACK_ROOT_SEGMENTS.has(segs[segs.length - 3]) && PROVIDER_SEGMENT_RE.test(provider) && !NOT_A_PROVIDER_SEGMENTS.has(provider)) {
22754
+ const authRootAtTop = below === 3 || segs[segs.length - 4] === "api" && segs[segs.length - 3] === "auth";
22755
+ if (segs.length >= 3 && authRootAtTop && CALLBACK_ROOT_SEGMENTS.has(segs[segs.length - 3]) && isProvider(provider)) {
21274
22756
  const step = segs[segs.length - 2];
21275
22757
  if (step === "callback" || step === "signin")
21276
22758
  return true;
21277
22759
  }
22760
+ if (below === 4 && CALLBACK_ROOT_SEGMENTS.has(segs[top]) && CALLBACK_PROTOCOL_SEGMENTS.has(segs[top + 1]) && isProvider(segs[top + 2]) && segs[top + 3] === "callback")
22761
+ return true;
21278
22762
  if (segs.length < 2 || segs[segs.length - 1] !== "callback")
21279
22763
  return false;
21280
22764
  const parent = segs[segs.length - 2];
@@ -21301,7 +22785,7 @@ var require_survey_network_guard = __commonJS({
21301
22785
  try {
21302
22786
  const a = new URL(url);
21303
22787
  const b = new URL(appBaseUrl);
21304
- const path = (u) => u.pathname.replace(/\/+$/, "");
22788
+ const path = (u) => (0, shared_1.stripTrailingSlashes)(u.pathname);
21305
22789
  return a.origin === b.origin && path(a) === path(b);
21306
22790
  } catch {
21307
22791
  return false;
@@ -21323,9 +22807,18 @@ var require_survey_network_guard = __commonJS({
21323
22807
  const bodyHash = (0, node_crypto_1.createHash)("sha256").update(req.postData ?? "").digest("hex").slice(0, 16);
21324
22808
  return `${record.method} ${url.hostname.toLowerCase()}${url.pathname} ${bodyHash}`;
21325
22809
  }
21326
- function isGraphqlGetWrite(url, s) {
21327
- const doc = url.searchParams.get("query");
21328
- return doc !== null && isGraphqlEndpoint(url, s) && !graphqlDocumentIsQueryOnly(doc);
22810
+ function graphqlUrlCarriesQuery(params) {
22811
+ return params.has("query");
22812
+ }
22813
+ function graphqlUrlQueriesAreReads(params) {
22814
+ return params.getAll("query").every((doc) => doc.trim() !== "" && graphqlDocumentIsQueryOnly(doc));
22815
+ }
22816
+ function graphqlGetMayWrite(url, body) {
22817
+ const docs = url.searchParams.getAll("query");
22818
+ return docs.length > 0 && (typeof body === "string" && body.length > 0 || docs.some((doc) => !graphqlDocumentIsQueryOnly(doc)));
22819
+ }
22820
+ function isGraphqlGetWrite(url, s, body) {
22821
+ return isGraphqlEndpoint(url, s) && graphqlGetMayWrite(url, body);
21329
22822
  }
21330
22823
  exports2.WRITE_VERBS = /* @__PURE__ */ new Set([
21331
22824
  "cancel",
@@ -21529,7 +23022,7 @@ var require_survey_network_guard = __commonJS({
21529
23022
  if (url.protocol !== "http:" && url.protocol !== "https:")
21530
23023
  return { action: "allow", rule: readMethod ? "read" : "non-http" };
21531
23024
  const appSite = isAppSite(url.hostname, s.appBaseUrl);
21532
- const graphqlGet = method === "GET" && isGraphqlGetWrite(url, s);
23025
+ const graphqlGet = method === "GET" && isGraphqlGetWrite(url, s, req.postData);
21533
23026
  const inActionWindow = s.lastActionAt > 0 && s.now - s.lastActionAt <= ACTION_NAVIGATION_WINDOW_MS;
21534
23027
  const typedLoad = s.agentLoad === true && req.resourceType === "document";
21535
23028
  const writeShaped = method === "GET" && appSite && !graphqlGet && (inActionWindow || typedLoad) ? writeShapedGet(req.url, {
@@ -21563,7 +23056,7 @@ var require_survey_network_guard = __commonJS({
21563
23056
  return { action: "allow", rule: "extra-pattern", reason: extra.reason, record };
21564
23057
  if (graphqlGet)
21565
23058
  return { action: "block", rule: "app-write", record };
21566
- if (method === "POST" && isGraphqlEndpoint(url, s) && isGraphqlQueryOnly(req.postData))
23059
+ if (method === "POST" && isGraphqlEndpoint(url, s) && graphqlUrlQueriesAreReads(url.searchParams) && isGraphqlQueryOnly(req.postData))
21567
23060
  return { action: "allow", rule: "graphql-query", record };
21568
23061
  const passive = s.lastNavigationAt > 0 && (s.lastActionAt === 0 || s.lastNavigationAt >= s.lastActionAt) && s.now - s.lastNavigationAt <= s.passiveWindowMs;
21569
23062
  const signature = learnableSignature(req, url, record, s);
@@ -21595,7 +23088,7 @@ var require_survey_network_guard = __commonJS({
21595
23088
  const re = new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""));
21596
23089
  return { reason, test: (hostPath) => re.test(hostPath) };
21597
23090
  }
21598
- const p = pattern.trim().toLowerCase().replace(/\/+$/, "");
23091
+ const p = (0, shared_1.stripTrailingSlashes)(pattern.trim().toLowerCase());
21599
23092
  if (p.includes("/"))
21600
23093
  return { reason, test: (hostPath) => hostPath.toLowerCase() === p || hostPath.toLowerCase().startsWith(`${p}/`) };
21601
23094
  return { reason, test: (hostPath) => {
@@ -22143,6 +23636,7 @@ var require_survey_repo_route_frontier = __commonJS({
22143
23636
  exports2.buildRepoRouteCandidates = buildRepoRouteCandidates;
22144
23637
  exports2.routeIdsHiddenFromChecklist = routeIdsHiddenFromChecklist;
22145
23638
  exports2.parseRepoRouteCoverage = parseRepoRouteCoverage;
23639
+ var shared_1 = (init_dist(), __toCommonJS(dist_exports));
22146
23640
  var route_manifest_js_1 = require_route_manifest();
22147
23641
  var survey_scope_js_1 = require_survey_scope();
22148
23642
  var repo_route_safety_js_1 = require_repo_route_safety();
@@ -22204,7 +23698,7 @@ var require_survey_repo_route_frontier = __commonJS({
22204
23698
  return route;
22205
23699
  try {
22206
23700
  const base = new URL(baseUrl);
22207
- return `${base.pathname.replace(/\/+$/, "") || "/"}${route}`;
23701
+ return `${(0, shared_1.stripTrailingSlashes)(base.pathname) || "/"}${route}`;
22208
23702
  } catch {
22209
23703
  return `/${route}`;
22210
23704
  }
@@ -22435,7 +23929,7 @@ var require_survey_repo_route_frontier = __commonJS({
22435
23929
  try {
22436
23930
  const base = new URL(input.baseUrl);
22437
23931
  origin = base.origin;
22438
- basePathname = base.pathname.replace(/\/+$/, "") || "/";
23932
+ basePathname = (0, shared_1.stripTrailingSlashes)(base.pathname) || "/";
22439
23933
  } catch {
22440
23934
  origin = null;
22441
23935
  }
@@ -22468,7 +23962,7 @@ var require_survey_repo_route_frontier = __commonJS({
22468
23962
  return null;
22469
23963
  if (parsed.origin !== this.appOrigin)
22470
23964
  return null;
22471
- const pathname = parsed.pathname.replace(/\/+$/, "") || "/";
23965
+ const pathname = (0, shared_1.stripTrailingSlashes)(parsed.pathname) || "/";
22472
23966
  const key = manifestRouteKey(`${parsed.pathname}${parsed.search}${parsed.hash}`);
22473
23967
  if (!key)
22474
23968
  return null;
@@ -22933,6 +24427,7 @@ var require_repo_feature_verification = __commonJS({
22933
24427
  exports2.parseBrowserVerificationRequirement = parseBrowserVerificationRequirement;
22934
24428
  exports2.parseBrowserVerificationResult = parseBrowserVerificationResult;
22935
24429
  exports2.renderBrowserVerificationForPrompt = renderBrowserVerificationForPrompt;
24430
+ var shared_1 = (init_dist(), __toCommonJS(dist_exports));
22936
24431
  var survey_repo_route_frontier_js_1 = require_survey_repo_route_frontier();
22937
24432
  var ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:\/\//i;
22938
24433
  function originOf(url) {
@@ -22989,7 +24484,7 @@ var require_repo_feature_verification = __commonJS({
22989
24484
  before = before.split("?")[0];
22990
24485
  if (!before.startsWith("/"))
22991
24486
  return null;
22992
- return before.replace(/\/+$/, "");
24487
+ return (0, shared_1.stripTrailingSlashes)(before);
22993
24488
  }
22994
24489
  function sameHashMount(route, entry) {
22995
24490
  const entryMount = hashMount(entry);
@@ -23045,7 +24540,7 @@ var require_repo_feature_verification = __commonJS({
23045
24540
  function compileEntryPattern(pattern) {
23046
24541
  const trimmed = String(pattern ?? "").trim();
23047
24542
  const hashAt = trimmed.indexOf("#/");
23048
- const raw = (hashAt >= 0 || trimmed.startsWith("#") ? trimmed.slice(Math.max(hashAt, 0)).split("?")[0] : trimmed.split(/[?#]/)[0]).replace(/\/+$/, "");
24543
+ const raw = (0, shared_1.stripTrailingSlashes)(hashAt >= 0 || trimmed.startsWith("#") ? trimmed.slice(Math.max(hashAt, 0)).split("?")[0] : trimmed.split(/[?#]/)[0]);
23049
24544
  if (!isEntrySpelling(raw) || raw.length > 500)
23050
24545
  return null;
23051
24546
  let out = "";