@validate.qa/runner 1.0.101 → 1.0.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +3 -0
  2. package/dist/{chunk-SN3PN4G4.mjs → chunk-36AWDKPL.mjs} +6 -2
  3. package/dist/{chunk-CYWHPXNZ.mjs → chunk-7SH3COMQ.mjs} +1 -1
  4. package/dist/{chunk-BBTK6OX6.mjs → chunk-BECCGMU4.mjs} +383 -11
  5. package/dist/{chunk-4SZC5ZX3.mjs → chunk-CG75I3ZZ.mjs} +25477 -5374
  6. package/dist/{chunk-TPU42VH2.mjs → chunk-CMM4DOXC.mjs} +10 -5
  7. package/dist/{chunk-M3VZGHZQ.mjs → chunk-NNJYKR3O.mjs} +22 -11
  8. package/dist/{chunk-IGXGSDE3.mjs → chunk-OTKJZIOD.mjs} +1298 -384
  9. package/dist/{chunk-CO5KHIVX.mjs → chunk-TK5KEGNW.mjs} +1 -1
  10. package/dist/{chunk-FQUH2PU2.mjs → chunk-WVLBQDFP.mjs} +140 -7
  11. package/dist/{chunk-2OKZY3WN.mjs → chunk-YI3ZC2XI.mjs} +2 -2
  12. package/dist/{chunk-XZUUBMP3.mjs → chunk-YIM746WO.mjs} +1 -1
  13. package/dist/cli.js +140692 -118903
  14. package/dist/cli.mjs +362 -108
  15. package/dist/credential-mcp-server.js +1402 -350
  16. package/dist/credential-mcp-server.mjs +3 -3
  17. package/dist/discovery-mcp-server.js +1330 -401
  18. package/dist/discovery-mcp-server.mjs +4 -4
  19. package/dist/dist-2G7YTNH5.mjs +17 -0
  20. package/dist/inbox-mcp-server.js +1676 -629
  21. package/dist/inbox-mcp-server.mjs +3 -3
  22. package/dist/route-seed-worker.js +1244 -330
  23. package/dist/route-seed-worker.mjs +4 -4
  24. package/dist/run-write-guard-preload.js +1622 -332
  25. package/dist/run-write-guard-preload.mjs +4 -4
  26. package/dist/run-write-guard.js +1622 -332
  27. package/dist/run-write-guard.mjs +3 -3
  28. package/dist/spec-driver.js +1 -1
  29. package/dist/spec-driver.mjs +1 -1
  30. package/package.json +3 -2
  31. package/dist/dist-VTPBY2C4.mjs +0 -17
@@ -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.",
@@ -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
 
@@ -4651,22 +4682,37 @@ function classifyAuthHandoffIntent(text) {
4651
4682
  function withoutIdpUrlAsserts(code) {
4652
4683
  return code.replace(/\.(?:toHaveURL|waitForURL)\s*\((?:[^)(]|\([^)]*\))*\)/g, (match) => /accountchooser|accounts\.google|appleid\.apple|login\.microsoftonline|\/v\d+\/signin|oauth/i.test(match) ? "" : match);
4653
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
+ }
4654
4696
  function stripComments(code) {
4655
- return replaceBlockComments(code, () => "").split("\n").map((line) => line.replace(/\/\/.*$/, "")).join("\n");
4697
+ return replaceBlockComments(code, () => "").split("\n").map(stripLineComment).join("\n");
4656
4698
  }
4657
- function balancedCall(code, start) {
4658
- const open = code.indexOf("(", start);
4659
- if (open < 0)
4660
- return null;
4661
- let depth = 0;
4662
- 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++) {
4663
4703
  const c = code[i];
4664
4704
  if (c === "(")
4665
- depth++;
4666
- else if (c === ")" && --depth === 0)
4667
- 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;
4668
4708
  }
4669
- 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] };
4670
4716
  }
4671
4717
  function resolveLocator(code, subject) {
4672
4718
  const id = subject.trim().match(/^([A-Za-z_$][\w$]*)$/);
@@ -4714,8 +4760,10 @@ function isAuthHandoffDismissCode(code) {
4714
4760
  const expects = [];
4715
4761
  const re = new RegExp(EXPECT_CALL_RE.source, "g");
4716
4762
  let match;
4763
+ let close = null;
4717
4764
  while (match = re.exec(body)) {
4718
- const found = balancedCall(body, match.index);
4765
+ close ??= closingParens(body);
4766
+ const found = balancedCall(body, close, match.index);
4719
4767
  if (found)
4720
4768
  expects.push({ inner: found.inner, tail: body.slice(found.end, found.end + 48) });
4721
4769
  }
@@ -4760,12 +4808,209 @@ var init_auth_handoff_coverage = __esm({
4760
4808
  }
4761
4809
  });
4762
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
+
4763
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
+ }
4764
5009
  function liveExpectStatements(uncommented) {
4765
5010
  const masked = maskedOutsideStrings(uncommented).split("\n");
4766
5011
  return accumulateStatements(uncommented.split("\n")).filter((s) => EXPECT_STMT.test(s.stmt) && EXPECT_STMT.test(masked[s.startLine - 1] ?? ""));
4767
5012
  }
4768
- function stripLineComment(ln) {
5013
+ function stripLineComment2(ln) {
4769
5014
  let quote = null;
4770
5015
  for (let i = 0; i < ln.length; i++) {
4771
5016
  const c = ln[i];
@@ -4788,7 +5033,7 @@ function splitInteractionStatementsWithLines(lines) {
4788
5033
  let depth = 0;
4789
5034
  let scaffold = false;
4790
5035
  for (let i = 0; i < lines.length; i++) {
4791
- const ln = stripLineComment(lines[i]).trim();
5036
+ const ln = stripLineComment2(lines[i]).trim();
4792
5037
  if (!ln)
4793
5038
  continue;
4794
5039
  const isContinuation = out.length > 0 && (ln.startsWith(".") || depth > 0 && !scaffold);
@@ -4812,7 +5057,7 @@ function testBlockIds(lines) {
4812
5057
  let any = false;
4813
5058
  let block = 0;
4814
5059
  for (let i = 0; i < lines.length; i++) {
4815
- const ln = stripLineComment(lines[i]).trim();
5060
+ const ln = stripLineComment2(lines[i]).trim();
4816
5061
  if (!open) {
4817
5062
  if (!TEST_BLOCK_OPEN.test(ln))
4818
5063
  continue;
@@ -4896,7 +5141,7 @@ function expectSubjectSpan(stmt) {
4896
5141
  }
4897
5142
  return null;
4898
5143
  }
4899
- function resolveSubjectBindings(stmts, bindings, blockOfLine) {
5144
+ function resolveSubjectBindings(stmts, bindings, blockOfLine, boundAt) {
4900
5145
  if (bindings.length === 0)
4901
5146
  return [...stmts];
4902
5147
  return stmts.map((item) => {
@@ -4919,14 +5164,22 @@ function resolveSubjectBindings(stmts, bindings, blockOfLine) {
4919
5164
  subject = `${lead[1]}${found.expr.replace(/;\s*$/, "")}${subject.slice(lead[0].length)}`;
4920
5165
  line = found.line;
4921
5166
  }
4922
- 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;
4923
5171
  });
4924
5172
  }
4925
5173
  function stripLoginScaffolding(code) {
4926
- 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 = [];
4927
5179
  let inAuth = false;
4928
5180
  let inInbox = false;
4929
- for (const ln of code.split("\n")) {
5181
+ for (let i = 0; i < lines.length; i++) {
5182
+ const ln = lines[i];
4930
5183
  if (!inAuth && !inInbox && AUTH_START.test(ln)) {
4931
5184
  inAuth = true;
4932
5185
  continue;
@@ -4947,9 +5200,9 @@ function stripLoginScaffolding(code) {
4947
5200
  }
4948
5201
  if (LOGIN_CALL_LINE.test(ln))
4949
5202
  continue;
4950
- out.push(ln);
5203
+ kept.push(i);
4951
5204
  }
4952
- return out.join("\n");
5205
+ return kept;
4953
5206
  }
4954
5207
  function stripCommentsAndLogs(code) {
4955
5208
  return stripCoverageComments(code).split("\n").filter((ln) => !/^\s*console\./.test(ln)).join("\n");
@@ -5101,6 +5354,41 @@ function isVacuousErrorAbsence(stmt, presence) {
5101
5354
  return false;
5102
5355
  return !presence().includes(m[1]);
5103
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
+ }
5104
5392
  function isBareContainerVisibility(stmt) {
5105
5393
  return BARE_CONTAINER_ROLE.test(stmt) && !GET_BY_TEXT.test(stmt) && !HAS_TEXT_FILTER.test(stmt) && !TEXT_CONTENT_MATCHER.test(stmt);
5106
5394
  }
@@ -5182,7 +5470,7 @@ function roleNames(s) {
5182
5470
  function interactedControlNames(lines) {
5183
5471
  const names = /* @__PURE__ */ new Set();
5184
5472
  for (const raw of lines) {
5185
- const ln = stripLineComment(raw);
5473
+ const ln = stripLineComment2(raw);
5186
5474
  if (!INTERACT_VERB.test(ln))
5187
5475
  continue;
5188
5476
  for (const n of roleNames(ln))
@@ -5378,6 +5666,14 @@ function balancedArguments(code, from, limit = code.length) {
5378
5666
  }
5379
5667
  return null;
5380
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
+ }
5381
5677
  function matcherArgumentsAreConstant(body, from) {
5382
5678
  MATCHER_OPEN.lastIndex = from;
5383
5679
  const opened = MATCHER_OPEN.exec(body);
@@ -5419,9 +5715,12 @@ function envValueKey(name) {
5419
5715
  function enteredValues(stmts) {
5420
5716
  const values = /* @__PURE__ */ new Map();
5421
5717
  const navLines = [];
5718
+ const accountConsts = /* @__PURE__ */ new Set();
5422
5719
  for (const { stmt, startLine } of stmts) {
5423
5720
  if (PAGE_NAV.test(stmt))
5424
5721
  navLines.push(startLine);
5722
+ for (const c of stmt.matchAll(ACCOUNT_CONST))
5723
+ accountConsts.add(c[1].replace(/\$/g, "\\$"));
5425
5724
  let m;
5426
5725
  ENTERED_VALUE.lastIndex = 0;
5427
5726
  const add = (value) => {
@@ -5437,7 +5736,8 @@ function enteredValues(stmts) {
5437
5736
  while (m = ENTERED_ENV.exec(stmt))
5438
5737
  add(envValueKey(m[1] ?? m[2] ?? m[3] ?? m[4]));
5439
5738
  }
5440
- return { values, navLines };
5739
+ const consts = accountConsts.size > 0 ? `|(?<![\\w$.])(?:${[...accountConsts].join("|")})(?![\\w$])` : "";
5740
+ return { values, navLines, account: new RegExp(`${ACCOUNT_ENV}${consts}`) };
5441
5741
  }
5442
5742
  function countBelow(lines, line) {
5443
5743
  let lo = 0;
@@ -5574,6 +5874,9 @@ function comparesWithItself(subject, tail) {
5574
5874
  return expected === squash2(m[1] === "toHaveCount" ? `${subject}.count()` : subject);
5575
5875
  }
5576
5876
  function asPlainAssertion(s) {
5877
+ return plainAssertion(s, false);
5878
+ }
5879
+ function plainAssertion(s, keepSelfCompare) {
5577
5880
  const { stmt } = s;
5578
5881
  if (COUNT_AT_LEAST_ZERO.test(stmt))
5579
5882
  return [];
@@ -5582,13 +5885,13 @@ function asPlainAssertion(s) {
5582
5885
  return [s];
5583
5886
  const plain = subject.replace(/^await\s+/, "").trim();
5584
5887
  const subjectAt = stmt.indexOf(subject, stmt.search(EXPECT_CALL_RE));
5585
- const tail = stmt.slice(subjectAt + subject.length).trimStart();
5888
+ const tail = (stmt.startsWith("expect.poll", stmt.search(EXPECT_CALL_RE)) ? expectCallTail(stmt) : null) ?? stmt.slice(subjectAt + subject.length).trimStart();
5586
5889
  const as = (rewritten) => [{ ...s, stmt: rewritten }];
5587
5890
  if (LOCATOR_EXPR.test(plain) && !LOCATOR_VALUE_READ.test(plain) && ALWAYS_TRUE_ON_OBJECT.test(tail))
5588
5891
  return [];
5589
5892
  if (DEFINED_VALUE_READ.test(plain) && ALWAYS_DEFINED.test(tail))
5590
5893
  return [];
5591
- if (comparesWithItself(plain, tail))
5894
+ if (!keepSelfCompare && comparesWithItself(plain, tail))
5592
5895
  return [];
5593
5896
  const visible = /^(.+)\.\s*isVisible\s*\(\s*\)$/s.exec(plain);
5594
5897
  if (visible && IS_VISIBLE_TRUE.test(tail))
@@ -5599,6 +5902,9 @@ function asPlainAssertion(s) {
5599
5902
  }
5600
5903
  return [s];
5601
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
+ }
5602
5908
  function creditsOutcomeAlways(stmt, entered, startLine) {
5603
5909
  if (!OUTCOME_ALWAYS.some((re) => re.test(stmt)))
5604
5910
  return false;
@@ -5631,6 +5937,22 @@ function successNoticeQuote(stmt) {
5631
5937
  }
5632
5938
  return null;
5633
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
+ }
5634
5956
  function provesStoredControlValue(stmt, startLine, persistence) {
5635
5957
  if (!persistence || startLine === void 0)
5636
5958
  return false;
@@ -5657,7 +5979,64 @@ function reopenedAfterSave(startLine, persistence, reopens = persistence.reloadL
5657
5979
  }
5658
5980
  return lo < reopens.length && reopens[lo] < startLine;
5659
5981
  }
5660
- 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) {
5661
6040
  if (mutated && hasAttributeTransition(stmts))
5662
6041
  return { kind: "durable" };
5663
6042
  const notices = [];
@@ -5667,6 +6046,13 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
5667
6046
  };
5668
6047
  let presenceText;
5669
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
+ };
5670
6056
  for (const { stmt, startLine } of stmts) {
5671
6057
  if (firstMutLine !== void 0 && startLine !== void 0 && startLine < firstMutLine)
5672
6058
  continue;
@@ -5674,6 +6060,11 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
5674
6060
  continue;
5675
6061
  if (COUNT_ONE.test(stmt) && quotedAssertedStrings(stmt).some((t) => clicked.has(t.toLowerCase())))
5676
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
+ }
5677
6068
  if (creditsOutcomeAlways(stmt, entered, startLine))
5678
6069
  return { kind: "durable" };
5679
6070
  if (mutated && !NEGATED_ASSERT.test(stmt) && MESSAGE_SURFACE.test(stmt) && OUTCOME_TEXT.test(assertedText(stmt))) {
@@ -5755,6 +6146,53 @@ function noticeOnlyExemption(clicked, noticeText, interactionStmts) {
5755
6146
  function detectOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine) {
5756
6147
  return classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine).kind !== "none";
5757
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
+ }
5758
6196
  function validateAssertionStrength(code, opts = {}) {
5759
6197
  const body = stripLoginScaffolding(code);
5760
6198
  const lines = body.split("\n");
@@ -5798,7 +6236,13 @@ function validateAssertionStrength(code, opts = {}) {
5798
6236
  const interactionStmts = inlineLocatorBindings(interactionStmtsRaw);
5799
6237
  const blockIds = testBlockIds(lines);
5800
6238
  const blockOfLine = (line) => blockIds?.[line - 1] ?? 0;
5801
- 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
+ });
5802
6246
  const insideTest = linesInsideTestBlocks(lines);
5803
6247
  const inTestOrder = (i) => insideTest === null || insideTest[interactionStmtsWithLines[i].startLine - 1] === true;
5804
6248
  let mutated = opts.interactionPerformed === true;
@@ -5808,9 +6252,34 @@ function validateAssertionStrength(code, opts = {}) {
5808
6252
  let navClicks = 0;
5809
6253
  const persistence = { mutLines: [], reloadLines: [], reloadCalls: [] };
5810
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
+ };
5811
6278
  for (let i = 0; i < interactionStmts.length; i++) {
5812
6279
  const ln = interactionStmts[i];
5813
6280
  const at = interactionStmtsWithLines[i].startLine;
6281
+ if (teardown !== null && acts.last?.kind === "fill" && ENTER_PRESS.test(ln))
6282
+ noteAct(i, at, "enter");
5814
6283
  if (RELOAD.test(ln)) {
5815
6284
  persistence.reloadLines.push(at);
5816
6285
  persistence.reloadCalls.push(at);
@@ -5818,32 +6287,31 @@ function validateAssertionStrength(code, opts = {}) {
5818
6287
  const opened = ln.match(/\.goto\s*\(\s*([^,)]+)/);
5819
6288
  if (opened) {
5820
6289
  const page = opened[1].trim();
5821
- if (openedPages.has(page))
6290
+ const path = namedPagePath(page);
6291
+ if (openedPages.has(page) || path !== null && savedOnCheckedPage.has(path))
5822
6292
  persistence.reloadLines.push(at);
5823
- else
5824
- openedPages.add(page);
6293
+ openedPages.add(page);
6294
+ checkedPage = null;
5825
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]);
5826
6301
  if (FORM_INPUT.test(ln)) {
5827
6302
  mutated = true;
5828
- lastMutIdx = i;
5829
- if (inTestOrder(i)) {
5830
- persistence.mutLines.push(at);
5831
- if (firstMutIdx < 0)
5832
- firstMutIdx = i;
5833
- }
6303
+ saveAt(i, at);
6304
+ noteAct(i, at, "fill");
5834
6305
  continue;
5835
6306
  }
5836
6307
  if (ANY_CLICK.test(ln)) {
5837
6308
  if (NAV_LOCATOR.test(ln)) {
5838
6309
  navClicks++;
6310
+ checkedPage = null;
5839
6311
  } else {
5840
6312
  mutated = true;
5841
- lastMutIdx = i;
5842
- if (inTestOrder(i)) {
5843
- persistence.mutLines.push(at);
5844
- if (firstMutIdx < 0)
5845
- firstMutIdx = i;
5846
- }
6313
+ saveAt(i, at);
6314
+ noteAct(i, at, "click");
5847
6315
  }
5848
6316
  }
5849
6317
  }
@@ -5876,9 +6344,28 @@ function validateAssertionStrength(code, opts = {}) {
5876
6344
  };
5877
6345
  }
5878
6346
  const orderedPersistence = firstMutLine !== void 0 ? persistence : void 0;
5879
- 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);
5880
6349
  if (proof.kind === "durable") {
5881
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
+ }
5882
6369
  return { ok: true, severity: "none", reason: "proves an outcome (count / diff / value / revealed content)", signals: signals2 };
5883
6370
  }
5884
6371
  if (mutated && proof.kind === "notice") {
@@ -5927,7 +6414,7 @@ function validateAssertionStrength(code, opts = {}) {
5927
6414
  signals
5928
6415
  };
5929
6416
  }
5930
- const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && URL_PROOF.test(s));
6417
+ const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && isUrlProof(s));
5931
6418
  if (urlProofAfter) {
5932
6419
  return {
5933
6420
  ok: false,
@@ -5964,7 +6451,7 @@ function validateAssertionStrength(code, opts = {}) {
5964
6451
  signals
5965
6452
  };
5966
6453
  }
5967
- var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, NEGATED_ASSERT_NOT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, VALUE_ABSENCE, OPTION_ABSENCE, NOT_BEFORE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, NEGATED_TEXT, ASYNC_DESTRUCTURE_OPEN, REQUEST_WORD, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, ENV_READ, ENTERED_ENV, HAVE_VALUE_ENV, IN_FLIGHT_TEXT, LOCATOR_VALUE_READ, LOCATOR_EXPR, ALWAYS_TRUE_ON_OBJECT, COUNT_AT_LEAST_ZERO, DEFINED_VALUE_READ, ALWAYS_DEFINED, SELF_COMPARE_MATCHER, IS_VISIBLE_TRUE, PAGE_VALUE_MATCHER, COUNT_ONE, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD;
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;
5968
6455
  var init_assertion_strength = __esm({
5969
6456
  "../shared/dist/assertion-strength.js"() {
5970
6457
  "use strict";
@@ -5973,6 +6460,7 @@ var init_assertion_strength = __esm({
5973
6460
  init_assertion_proof();
5974
6461
  init_auth_handoff_coverage();
5975
6462
  init_expect_call();
6463
+ init_auth_plan();
5976
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;
5977
6465
  OUTCOME_ALWAYS = [
5978
6466
  /\.toHaveCount\s*\(/,
@@ -5989,7 +6477,7 @@ var init_assertion_strength = __esm({
5989
6477
  // form field value
5990
6478
  /\.toMatch\s*\(/,
5991
6479
  // string/regex content match (e.g. textContent() vs /[2-9] selected/)
5992
- /(?<!\.not\s*)\.\s*toBeDisabled\s*\(/,
6480
+ /\.(?<!\.not\s*\.)\s*toBeDisabled\s*\(/,
5993
6481
  // a control became DISABLED — a gating/validation outcome (not.toBeDisabled is the toBeEnabled twin)
5994
6482
  /\.not\.toBeVisible\s*\(/,
5995
6483
  /\.toBeHidden\s*\(/
@@ -5998,12 +6486,16 @@ var init_assertion_strength = __esm({
5998
6486
  FORM_INPUT = /\.(fill|selectOption|setInputFiles|check|uncheck|setChecked|pressSequentially)\s*\(|keyboard\.type\s*\(/;
5999
6487
  ANY_CLICK = /\.click\s*\(/;
6000
6488
  NAV_LOCATOR = /getByRole\s*\(\s*['"`](?:link|menuitem)['"`]/i;
6001
- URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(|\bexpect\s*\(\s*(?:await\s+)?page\s*\.\s*url\s*\(\s*\)\s*\)|\bexpect\s*\.\s*poll\s*\(\s*(?:async\s*)?\(\s*\)\s*=>\s*(?:\{\s*return\s+)?page\s*\.\s*url\s*\(\s*\)/;
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;
6002
6494
  RELOAD = /\.reload\s*\(/;
6003
6495
  EXPECT_STMT = /\bexpect\s*[.(]/;
6004
6496
  BLOCK_SCAFFOLD2 = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
6005
6497
  TEST_BLOCK_OPEN = /^(?:test|it)(?:\.(?:only|skip|fixme|slow|serial|describe\.serial))?\s*\(|^(?:test\.)?(?:beforeEach|beforeAll)\s*\(/;
6006
- 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*\(.+)$/;
6007
6499
  EXPECT_OPEN = /\bexpect(?:\.soft)?\s*\(/;
6008
6500
  STATE_MATCHER = /\.(toBeChecked|toBeVisible|toHaveValue|toBeDisabled|toBeEnabled)\s*\(/;
6009
6501
  CHROME_LOCATOR = /getByRole\s*\(\s*['"`](?:heading|navigation|banner|main|contentinfo|complementary|region|search|tablist|tab|button|link|menuitem|textbox|searchbox|combobox|spinbutton)['"`]/i;
@@ -6023,7 +6515,7 @@ var init_assertion_strength = __esm({
6023
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;
6024
6516
  AUTH_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_AUTH/;
6025
6517
  AUTH_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_AUTH/;
6026
- 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*)?$/;
6027
6519
  INBOX_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_INBOX/;
6028
6520
  INBOX_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_INBOX/;
6029
6521
  NEGATED_ASSERT = /\.not\s*\.\s*to[A-Z]\w*|\.toBeHidden\s*\(/;
@@ -6033,7 +6525,7 @@ var init_assertion_strength = __esm({
6033
6525
  TITLE_LINE = /^\s*(?:test|describe)(?:\.\w+)?\s*\(/;
6034
6526
  FAILURE_STATUS_ASSERT = [
6035
6527
  /\.status\s*\(\s*\)[^;]{0,200}\.(?:toBeGreaterThanOrEqual|toBeGreaterThan)\s*\(\s*(?:399|[45]\d\d)\b/,
6036
- /\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)/,
6037
6529
  /\bexpect\s*\([^;]{0,200}\.ok\s*\(\s*\)\s*\)\s*\.\s*(?:toBe\s*\(\s*false\s*\)|toBeFalsy\s*\()/
6038
6530
  ];
6039
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;
@@ -6048,6 +6540,7 @@ var init_assertion_strength = __esm({
6048
6540
  NOT_BEFORE_MATCHER = /\.not\s*\.\s*(?=to[A-Z])/g;
6049
6541
  ERROR_TEXT_WORDS = /error|fail|wrong|try again|unable|could ?n.?t|can.?t|problem|invalid|unavailable/i;
6050
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;
6051
6544
  NEGATED_TEXT = /\.not\s*\.\s*(?:toHaveText|toContainText)\s*\(\s*(?:['"`]([^'"`]*)['"`]|\/((?:\\.|[^/\\\n])+)\/)?/;
6052
6545
  ASYNC_DESTRUCTURE_OPEN = /async\s*\(\s*\{/g;
6053
6546
  REQUEST_WORD = /\brequest\b/;
@@ -6074,6 +6567,9 @@ var init_assertion_strength = __esm({
6074
6567
  ENV_READ = String.raw`process\.env(?:(?:\?\.|\.)([A-Za-z_]\w*)|(?:\?\.)?\[\s*['"]([A-Za-z_]\w*)['"]\s*\])`;
6075
6568
  ENTERED_ENV = new RegExp(String.raw`\.(?:fill|pressSequentially)\s*\(\s*${ENV_READ}|keyboard\.type\s*\(\s*${ENV_READ}`, "g");
6076
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");
6077
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;
6078
6574
  LOCATOR_VALUE_READ = /\.(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts|evaluate|evaluateAll|boundingBox|all)\s*\(/;
6079
6575
  LOCATOR_EXPR = /^(?:this\.)?page\s*\.\s*(?:getBy\w+|locator)\s*\(/;
@@ -6085,20 +6581,47 @@ var init_assertion_strength = __esm({
6085
6581
  IS_VISIBLE_TRUE = /^\)*\s*\.\s*(?:toBe\s*\(\s*true\s*\)|toBeTruthy\s*\(\s*\))/;
6086
6582
  PAGE_VALUE_MATCHER = /^\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
6087
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]*)?$/;
6088
6586
  FORM_CONTROL_LOCATOR = /getByRole\s*\(\s*['"`](?:combobox|switch|checkbox|radio|textbox|searchbox|spinbutton|slider)['"`]|getByLabel\s*\(|getByPlaceholder\s*\(/;
6089
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*\(/;
6090
6596
  COPY_CONTROL = /\bcopy\b/i;
6091
6597
  COPIED_NOTICE = /\bcopied\b/i;
6092
6598
  SEND_CONTROL = /\b(send|submit|subscribe|sign up for|contact|feedback|join (the )?waitlist)\b/i;
6093
6599
  SEND_NOTICE = /\b(thank|thanks|sent|subscribed|received|in touch)\b/i;
6094
6600
  MARK_READ_CONTROL = /\bmark(?:ed)? as read\b|\bmark all(?: as)? read\b/i;
6095
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*\(/;
6096
6616
  }
6097
6617
  });
6098
6618
 
6099
6619
  // ../shared/dist/run-classification.js
6100
- function isUserCancelledError(error) {
6101
- 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));
6102
6625
  }
6103
6626
  function isHeldHealRun(run) {
6104
6627
  return run?.status === "PASSED" && typeof run.error === "string" && run.error.startsWith(HEAL_PATCH_HELD_RUN_ERROR_PREFIX);
@@ -6131,7 +6654,7 @@ function pickLatestHealthRun(runs) {
6131
6654
  for (const run of runs) {
6132
6655
  if (run.mode === "auth-setup")
6133
6656
  continue;
6134
- if (run.status === "ERROR" && isUserCancelledError(run.error))
6657
+ if (run.status === "ERROR" && isCancelledReason(run.error))
6135
6658
  continue;
6136
6659
  if (isHeldHealRun(run))
6137
6660
  continue;
@@ -6149,6 +6672,15 @@ function pickDisplayRun(runs) {
6149
6672
  return latest;
6150
6673
  return pickLatestHealthRun(runs) ?? latest;
6151
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
+ }
6152
6684
  function isEnvironmentalError(run) {
6153
6685
  if (!run)
6154
6686
  return false;
@@ -6180,7 +6712,7 @@ function classifyErrorMessage(error) {
6180
6712
  return "config_issue";
6181
6713
  if (EMAIL_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
6182
6714
  return "email_not_received";
6183
- if (isUserCancelledError(error))
6715
+ if (isCancelledReason(error))
6184
6716
  return "cancelled";
6185
6717
  if (isRetriedRunInterruption(error))
6186
6718
  return "interrupted";
@@ -6215,7 +6747,7 @@ function heldHealRunWhere() {
6215
6747
  function excludeCancelledRunsWhere() {
6216
6748
  return {
6217
6749
  NOT: [
6218
- { status: "ERROR", error: { not: null, startsWith: USER_CANCELLED_RUN_ERROR } },
6750
+ { status: "ERROR", OR: CANCELLED_REASON_PREFIXES.map((prefix) => ({ error: { not: null, startsWith: prefix } })) },
6219
6751
  heldHealRunWhere()
6220
6752
  ]
6221
6753
  };
@@ -6293,11 +6825,25 @@ function reportedBugWhere() {
6293
6825
  function bugReviewStatus(status) {
6294
6826
  return status === "QUARANTINED" ? "confirmed" : "needs-review";
6295
6827
  }
6296
- var USER_CANCELLED_RUN_ERROR, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, TEST_DID_NOT_RUN_ERROR, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
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;
6297
6829
  var init_run_classification = __esm({
6298
6830
  "../shared/dist/run-classification.js"() {
6299
6831
  "use strict";
6300
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;
6301
6847
  HEAL_PATCH_HELD_RUN_ERROR_PREFIX = "Heal patch held";
6302
6848
  RUNNER_INTERRUPT_ERROR_PREFIX = "Runner shutting down";
6303
6849
  SERVER_INTERRUPT_ERROR_PREFIX = "Run interrupted by server restart";
@@ -6326,9 +6872,10 @@ var init_run_classification = __esm({
6326
6872
  ];
6327
6873
  ENVIRONMENTAL_ERROR_STORED_PREFIXES = [
6328
6874
  ...ENVIRONMENTAL_ERROR_PREFIXES.flatMap((prefix) => [prefix, `Error: ${prefix}`]),
6329
- // Written by the cancel routes as is, with no prefix wrapper.
6330
- USER_CANCELLED_RUN_ERROR
6875
+ // Written by the cancels as is, with no prefix wrapper.
6876
+ ...CANCELLED_REASON_PREFIXES
6331
6877
  ];
6878
+ FEATURE_ROW_RUN_WINDOW = 10;
6332
6879
  BUG_SURFACE_SUPPRESSING_TAGS = ["@rate-limit-blocked", "@quota-exceeded", "@auth-lost"];
6333
6880
  QUARANTINE_CLOSE_TAGS = [
6334
6881
  "@bug-found",
@@ -8761,180 +9308,6 @@ var init_admin_view_return_to = __esm({
8761
9308
  }
8762
9309
  });
8763
9310
 
8764
- // ../shared/dist/auth-plan.js
8765
- function hasInlineLoginSteps(playwrightCode) {
8766
- if (!playwrightCode)
8767
- return false;
8768
- return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
8769
- }
8770
- function hasGeneratedAuthGuard(playwrightCode) {
8771
- if (!playwrightCode)
8772
- return false;
8773
- return /\/\/ >>> VALIDATE_QA_AUTH v(\d+)\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\1\b/.test(playwrightCode);
8774
- }
8775
- function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
8776
- if (!sharedAuthInjectEnabled)
8777
- return false;
8778
- if (tags.includes("@auth"))
8779
- return false;
8780
- if (tags.includes("@api"))
8781
- return true;
8782
- if (!tags.includes("@needs-auth"))
8783
- return false;
8784
- if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
8785
- return true;
8786
- const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
8787
- const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
8788
- return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
8789
- }
8790
- function codeReadsStoredCredentials(code) {
8791
- if (!code)
8792
- return false;
8793
- if (/__vqaLogin\b/.test(code))
8794
- return true;
8795
- for (const m of code.matchAll(/TEST_[A-Z0-9_]+/g)) {
8796
- if (/_(?:EMAIL|PASSWORD)/.test(m[0]))
8797
- return true;
8798
- }
8799
- for (const m of code.matchAll(/process\.env\s*\[([^\]]*)\]?/g)) {
8800
- if (!/^\s*(['"])[^'"`]*\1\s*$/.test(m[1]))
8801
- return true;
8802
- }
8803
- return false;
8804
- }
8805
- function explicitAuthTypeTags(t) {
8806
- const found = /* @__PURE__ */ new Set();
8807
- for (const tag of t) {
8808
- if (AUTH_TYPE_TAGS.includes(tag))
8809
- found.add(tag);
8810
- else if (Object.hasOwn(AUTH_TAG_ALIASES, tag))
8811
- found.add(AUTH_TAG_ALIASES[tag]);
8812
- }
8813
- return [...found];
8814
- }
8815
- function invalidPlan(configurationError) {
8816
- return {
8817
- version: 1,
8818
- source: "invalid",
8819
- acquisition: "unsupported",
8820
- presentation: "none",
8821
- needsCookieState: false,
8822
- needsStandingHeaders: false,
8823
- standingHeadersMode: "none",
8824
- needsLoginCreds: false,
8825
- requiredEnvKeys: [],
8826
- configurationError
8827
- };
8828
- }
8829
- function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
8830
- return {
8831
- version: 1,
8832
- source: "explicit",
8833
- authTypeTag: authTypeTag2,
8834
- acquisition,
8835
- presentation,
8836
- needsCookieState: opts.cookie,
8837
- needsStandingHeaders: opts.standingMode === "required",
8838
- standingHeadersMode: opts.standingMode,
8839
- needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
8840
- requiredEnvKeys: opts.requiredEnvKeys
8841
- };
8842
- }
8843
- function deriveAuthPlan(tags, code, opts) {
8844
- const t = Array.isArray(tags) ? tags : [];
8845
- const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
8846
- const isApi = t.includes("@api");
8847
- const explicit = isApi ? explicitAuthTypeTags(t) : [];
8848
- if (explicit.length > 0) {
8849
- if (t.includes("@auth")) {
8850
- return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
8851
- }
8852
- const loginCompose = explicit.filter((e) => e === AUTH_TAG_COOKIE_STATE || e === AUTH_TAG_LOGIN_COOKIE || e === AUTH_TAG_LOGIN_BEARER);
8853
- if (explicit.length === 2 && explicit.includes(AUTH_TAG_STANDING) && loginCompose.length === 1) {
8854
- const composedKeys = [...TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY];
8855
- switch (loginCompose[0]) {
8856
- case AUTH_TAG_COOKIE_STATE:
8857
- return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "required", requiredEnvKeys: composedKeys });
8858
- case AUTH_TAG_LOGIN_COOKIE:
8859
- return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
8860
- case AUTH_TAG_LOGIN_BEARER:
8861
- return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
8862
- }
8863
- }
8864
- if (explicit.length > 1) {
8865
- return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
8866
- }
8867
- switch (explicit[0]) {
8868
- case AUTH_TAG_UNSUPPORTED:
8869
- return {
8870
- ...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
8871
- };
8872
- case AUTH_TAG_NONE:
8873
- return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
8874
- case AUTH_TAG_STANDING:
8875
- return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
8876
- case AUTH_TAG_LOGIN_BEARER:
8877
- return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
8878
- case AUTH_TAG_LOGIN_COOKIE:
8879
- return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
8880
- case AUTH_TAG_COOKIE_STATE:
8881
- return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
8882
- }
8883
- }
8884
- const cookie = legacyNeedsCookieState(t, code, sharedFlag);
8885
- const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
8886
- const selfLoginWithoutStoredCreds = t.includes("@auth") && !t.includes("@needs-auth") && !isApi && !!code?.trim() && !/TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin\b/.test(code);
8887
- const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
8888
- const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
8889
- return {
8890
- version: 1,
8891
- source: "legacy",
8892
- acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
8893
- presentation: authedLegacy ? "cookie" : "none",
8894
- needsCookieState: cookie,
8895
- needsStandingHeaders: false,
8896
- // A legacy bare-@api test keeps injecting standing headers IF the project configured them
8897
- // (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
8898
- standingHeadersMode: isApi ? "legacy-if-configured" : "none",
8899
- needsLoginCreds,
8900
- requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
8901
- };
8902
- }
8903
- function authTypeTag(tags) {
8904
- const t = Array.isArray(tags) ? tags : [];
8905
- const explicit = explicitAuthTypeTags(t);
8906
- return explicit.length === 1 ? explicit[0] : void 0;
8907
- }
8908
- 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;
8909
- var init_auth_plan = __esm({
8910
- "../shared/dist/auth-plan.js"() {
8911
- "use strict";
8912
- AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
8913
- AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
8914
- AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
8915
- AUTH_TAG_STANDING = "@auth-standing";
8916
- AUTH_TAG_NONE = "@auth-none";
8917
- AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
8918
- AUTH_TYPE_TAGS = [
8919
- AUTH_TAG_COOKIE_STATE,
8920
- AUTH_TAG_LOGIN_COOKIE,
8921
- AUTH_TAG_LOGIN_BEARER,
8922
- AUTH_TAG_STANDING,
8923
- AUTH_TAG_NONE,
8924
- AUTH_TAG_UNSUPPORTED
8925
- ];
8926
- AUTH_TAG_ALIASES = {
8927
- "@auth-cookie": AUTH_TAG_COOKIE_STATE,
8928
- "@auth-bearer": AUTH_TAG_LOGIN_BEARER,
8929
- "@auth-token": AUTH_TAG_LOGIN_BEARER,
8930
- "@auth-api-key": AUTH_TAG_STANDING,
8931
- "@auth-standing-header": AUTH_TAG_STANDING
8932
- };
8933
- TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
8934
- API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
8935
- }
8936
- });
8937
-
8938
9311
  // ../shared/dist/auth-block-detector.js
8939
9312
  function stripEchoLines(text) {
8940
9313
  return text.split("\n").filter((line) => !ECHO_LINE_RE.test(line)).join("\n");
@@ -8942,13 +9315,16 @@ function stripEchoLines(text) {
8942
9315
  function matchesAny(text, patterns) {
8943
9316
  return patterns.some((p) => p.test(text));
8944
9317
  }
9318
+ function isIntentionalNegativeAuthTest(input) {
9319
+ const meta = [input.testName, input.description].filter(Boolean).join(" ");
9320
+ return matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH);
9321
+ }
8945
9322
  function detectAuthBlock(input) {
8946
9323
  const tags = input.tags ?? [];
8947
9324
  const isAuthed = tags.some((t) => AUTHED_TAGS.includes(t));
8948
9325
  if (!isAuthed)
8949
9326
  return NO_DETECTION;
8950
- const meta = [input.testName, input.description].filter(Boolean).join(" ");
8951
- if (matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH))
9327
+ if (isIntentionalNegativeAuthTest(input))
8952
9328
  return NO_DETECTION;
8953
9329
  const sources = [];
8954
9330
  if (input.error)
@@ -9051,7 +9427,7 @@ var init_auth_block_detector = __esm({
9051
9427
  /\b(reject|deny|denied|block(ed)?)\b[^\n]{0,20}\b(login|sign[\s-]?in|auth|credential|password)\b/i
9052
9428
  ];
9053
9429
  ECHO_LINE_RE = /^\s*(?:(?:📋|🎭|🌐|⚙️?)|(?:stderr:\s*|stdout:\s*)?Locator:)/u;
9054
- 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;
9055
9431
  }
9056
9432
  });
9057
9433
 
@@ -9992,12 +10368,70 @@ function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
9992
10368
  function isAuthReadinessGuardEnabled(value = process.env.AUTH_READINESS_GUARD) {
9993
10369
  return value?.trim().toLowerCase() !== "false";
9994
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
+ }
9995
10377
  var init_feature_flags = __esm({
9996
10378
  "../shared/dist/feature-flags.js"() {
9997
10379
  "use strict";
9998
10380
  }
9999
10381
  });
10000
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
+
10001
10435
  // ../shared/dist/analytics-url.js
10002
10436
  function isSensitiveQueryKey(key) {
10003
10437
  return SENSITIVE_QUERY_KEY_RE.test(key);
@@ -10351,14 +10785,9 @@ function isOpenCodeGoModelId(id) {
10351
10785
  function hasOpenCodeGoPrefix(id) {
10352
10786
  return (id?.trim() ?? "").startsWith(OPENCODE_GO_PREFIX);
10353
10787
  }
10354
- function hasOpenCodeGo2Prefix(id) {
10355
- return (id?.trim() ?? "").startsWith(OPENCODE_GO_2_PREFIX);
10356
- }
10357
10788
  function openCodeGoWireModel(id) {
10358
10789
  const trimmed = id?.trim() ?? "";
10359
- if (!isOpenCodeGoModelId(trimmed))
10360
- return null;
10361
- return trimmed.slice((hasOpenCodeGo2Prefix(trimmed) ? OPENCODE_GO_2_PREFIX : OPENCODE_GO_PREFIX).length);
10790
+ return isOpenCodeGoModelId(trimmed) ? trimmed.slice(OPENCODE_GO_PREFIX.length) : null;
10362
10791
  }
10363
10792
  function isZaiModelId(id) {
10364
10793
  const trimmed = id?.trim() ?? "";
@@ -10467,7 +10896,7 @@ function museCliContributorMode(env = process.env) {
10467
10896
  const raw = (env.MUSE_CLI_CONTRIBUTOR_MODE ?? "").trim().toLowerCase();
10468
10897
  return raw === "off" || raw === "false" || raw === "0" || raw === "no" ? "off" : "on";
10469
10898
  }
10470
- var CLI_ENGINE_MODEL_IDS, BROWSER_CLI_ENGINE_IDS, PIPELINE_STEPS, CLI_ENGINE_RULES, ENGINE_NAMES, OPENROUTER_PREFIX, OPENROUTER_MODEL_PATTERN, OPENCODE_GO_PREFIX, OPENCODE_GO_2_PREFIX, OPENCODE_GO_MODEL_PATTERN, ZAI_PREFIX, ZAI_MODEL_PATTERN;
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;
10471
10900
  var init_engine_spec = __esm({
10472
10901
  "../shared/dist/engine-spec.js"() {
10473
10902
  "use strict";
@@ -10526,8 +10955,7 @@ var init_engine_spec = __esm({
10526
10955
  OPENROUTER_PREFIX = "openrouter:";
10527
10956
  OPENROUTER_MODEL_PATTERN = /^openrouter:[a-z0-9][a-z0-9._-]{0,40}\/[A-Za-z0-9._:-]{1,100}$/;
10528
10957
  OPENCODE_GO_PREFIX = "opencode-go:";
10529
- OPENCODE_GO_2_PREFIX = "opencode-go-2:";
10530
- OPENCODE_GO_MODEL_PATTERN = /^opencode-go(?:-2)?:[a-z0-9][a-z0-9._-]{0,100}$/;
10958
+ OPENCODE_GO_MODEL_PATTERN = /^opencode-go:[a-z0-9][a-z0-9._-]{0,100}$/;
10531
10959
  ZAI_PREFIX = "zai:";
10532
10960
  ZAI_MODEL_PATTERN = /^zai:glm-[a-z0-9][a-z0-9._-]{0,60}$/;
10533
10961
  }
@@ -10852,14 +11280,19 @@ function has(map, key) {
10852
11280
  }
10853
11281
  function providerChainFor(modelId) {
10854
11282
  const key = chainKey(modelId);
11283
+ if (key && has(_overrideHops, key))
11284
+ return _overrideHops[key];
10855
11285
  return key && has(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
10856
11286
  }
10857
11287
  function providerChainLeadsFor(modelId) {
10858
11288
  const key = chainKey(modelId);
11289
+ if (key && has(_overrideLeads, key))
11290
+ return _overrideLeads[key];
10859
11291
  return key && has(PROVIDER_CHAIN_LEADS, key) ? PROVIDER_CHAIN_LEADS[key] : [];
10860
11292
  }
10861
11293
  function allChains() {
10862
- return [...Object.entries(PROVIDER_CHAINS), ...Object.entries(PROVIDER_CHAIN_LEADS)];
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)];
10863
11296
  }
10864
11297
  function isProviderChainHopId(modelId) {
10865
11298
  const id = modelId ?? "";
@@ -10876,14 +11309,24 @@ function providerChainHostOf(value, modelValue) {
10876
11309
  return "own";
10877
11310
  if (hasZaiPrefix(value))
10878
11311
  return "zai";
10879
- if (hasOpenCodeGo2Prefix(value))
10880
- return "opencode-go-2";
10881
11312
  if (hasOpenCodeGoPrefix(value))
10882
11313
  return "opencode-go";
10883
11314
  if (hasOpenRouterPrefix(value))
10884
11315
  return "openrouter";
10885
11316
  return "own";
10886
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
+ }
10887
11330
  function providerHostPlan(modelId) {
10888
11331
  const key = chainKey(modelId);
10889
11332
  if (!key)
@@ -10901,7 +11344,140 @@ function providerHostPlan(modelId) {
10901
11344
  }
10902
11345
  return steps;
10903
11346
  }
10904
- var PROVIDER_CHAINS, PROVIDER_CHAIN_LEADS, PROVIDER_HOST_NOT_OFFERED;
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))
11359
+ return [];
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;
11461
+ }
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;
11476
+ }
11477
+ function getProviderChainOverrides() {
11478
+ return _overrides;
11479
+ }
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;
10905
11481
  var init_provider_chains = __esm({
10906
11482
  "../shared/dist/provider-chains.js"() {
10907
11483
  "use strict";
@@ -10913,18 +11489,120 @@ var init_provider_chains = __esm({
10913
11489
  "muse-spark-1.3-contributor": ["openrouter:meta/muse-spark-1.3-contributor"],
10914
11490
  "claude-haiku-5-5": ["opencode-go:claude-haiku-5-5", "openrouter:anthropic/claude-haiku-5.5"],
10915
11491
  "gemini-3.1-pro-preview": ["openrouter:google/gemini-3.1-pro-preview"],
10916
- "deepseek-flash": ["opencode-go-2:deepseek-v4.1-flash", "opencode-go:deepseek-v4.1-flash", "openrouter:deepseek/deepseek-v4.1-flash"],
10917
- "deepseek-v4-pro": ["opencode-go-2:deepseek-v4-pro", "opencode-go:deepseek-v4-pro", "openrouter:deepseek/deepseek-v4-pro"]
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"]
10918
11494
  };
10919
11495
  PROVIDER_CHAIN_LEADS = {
10920
- "openrouter:z-ai/glm-5.3-flash": ["zai:glm-5.3-flash", "opencode-go-2:glm-5.3-flash", "opencode-go:glm-5.3-flash"],
10921
- "openrouter:z-ai/glm-5.3": ["zai:glm-5.3", "opencode-go-2:glm-5.3", "opencode-go:glm-5.3"],
10922
- "openrouter:z-ai/glm-5.2": ["zai:glm-5.2", "opencode-go-2:glm-5.2", "opencode-go:glm-5.2"]
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"]
10923
11499
  };
10924
11500
  PROVIDER_HOST_NOT_OFFERED = {
10925
11501
  "muse-spark-1.3-contributor": { "opencode-go": "OpenCode Go serves Muse only on its Responses API" },
10926
11502
  "gemini-3.1-pro-preview": { "opencode-go": "OpenCode Go does not serve Gemini" }
10927
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:"];
10928
11606
  }
10929
11607
  });
10930
11608
 
@@ -11043,6 +11721,25 @@ var init_project_engine_overrides = __esm({
11043
11721
  }
11044
11722
  });
11045
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
+
11046
11743
  // ../shared/dist/gemini-visual.js
11047
11744
  function visualProDailyBudget(raw) {
11048
11745
  const parsed = raw && /^\s*\d{1,6}\s*$/.test(raw) ? Number(raw.trim()) : NaN;
@@ -11056,7 +11753,7 @@ function isRetiredGeminiModel(id) {
11056
11753
  }
11057
11754
  function geminiNativeModelsUrl(base = process.env.GEMINI_API_BASE) {
11058
11755
  const raw = base && base.trim() || GEMINI_OPENAI_BASE;
11059
- const native = raw.replace(/\/openai\/?$/, "/").replace(/\/+$/, "");
11756
+ const native = stripTrailingSlashes(raw.replace(/\/openai\/?$/, "/"));
11060
11757
  return `${native}/models?pageSize=1000`;
11061
11758
  }
11062
11759
  function asRecord(value) {
@@ -11176,6 +11873,7 @@ var KNOWN_INPUT_USD_PER_M, GEMINI_OPENAI_BASE, DEFAULT_GEMINI_SCREEN_MODEL, DEFA
11176
11873
  var init_gemini_visual = __esm({
11177
11874
  "../shared/dist/gemini-visual.js"() {
11178
11875
  "use strict";
11876
+ init_trailing_slashes();
11179
11877
  KNOWN_INPUT_USD_PER_M = {
11180
11878
  "gemini-2.5-flash-lite": 0.1,
11181
11879
  "gemini-3.1-flash-lite": 0.25,
@@ -12539,12 +13237,13 @@ function truncateAtWord(text, max) {
12539
13237
  const slice = trimmed.slice(0, room + 1);
12540
13238
  const lastSpace = slice.lastIndexOf(" ");
12541
13239
  const cut = lastSpace > room * 0.5 ? slice.slice(0, lastSpace) : trimmed.slice(0, room);
12542
- return `${cut.replace(/[\s,;:.\-–—]+$/u, "")}\u2026`;
13240
+ return `${stripTrailingChars(cut, TRAILING_PUNCTUATION)}\u2026`;
12543
13241
  }
12544
- 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;
12545
13243
  var init_canvas_support = __esm({
12546
13244
  "../shared/dist/canvas-support.js"() {
12547
13245
  "use strict";
13246
+ init_trailing_slashes();
12548
13247
  CANVAS_UNSUPPORTED_CODE = "canvas_unsupported";
12549
13248
  CANVAS_UNSUPPORTED_TITLE = "Games and canvas apps aren't supported yet";
12550
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.";
@@ -12669,6 +13368,7 @@ var init_canvas_support = __esm({
12669
13368
  TOUCH_NONE_RE = /touch-action\s{0,8}:\s{0,8}none/;
12670
13369
  CODE_TAGS = /* @__PURE__ */ new Set(["script", "link", "flt-glass-pane", "flutter-view", "flt-scene-host"]);
12671
13370
  STATIC_STANDARD_MIN_INTERACTIVE = 3;
13371
+ TRAILING_PUNCTUATION = /[\s,;:.\-–—]/u;
12672
13372
  }
12673
13373
  });
12674
13374
 
@@ -12851,22 +13551,22 @@ function joinChainedLines(code) {
12851
13551
  const trimmed = lines[i].trimStart();
12852
13552
  if (last !== -1 && trimmed.startsWith(".") && !trimmed.startsWith("...")) {
12853
13553
  if (!lastStripped) {
12854
- lines[last] = stripLineComment2(lines[last]).trimEnd();
13554
+ lines[last] = stripLineComment3(lines[last]).trimEnd();
12855
13555
  lastStripped = true;
12856
13556
  }
12857
13557
  if (lines[last].length <= MAX_LINE_CHARS)
12858
- lines[last] = `${lines[last]} ${stripLineComment2(trimmed).trimEnd()}`;
13558
+ lines[last] = `${lines[last]} ${stripLineComment3(trimmed).trimEnd()}`;
12859
13559
  lines[i] = "";
12860
13560
  continue;
12861
13561
  }
12862
- if (stripLineComment2(lines[i]).trim()) {
13562
+ if (stripLineComment3(lines[i]).trim()) {
12863
13563
  last = i;
12864
13564
  lastStripped = false;
12865
13565
  }
12866
13566
  }
12867
13567
  return lines.join("\n");
12868
13568
  }
12869
- function stripLineComment2(rawLine) {
13569
+ function stripLineComment3(rawLine) {
12870
13570
  if (rawLine.trimStart().startsWith("//"))
12871
13571
  return "";
12872
13572
  const at = rawLine.indexOf(" // ");
@@ -12915,7 +13615,7 @@ function analyzeReadinessPolling(rawCode) {
12915
13615
  offset += rawLine.length + 1;
12916
13616
  if (rawLine.length > MAX_LINE_CHARS)
12917
13617
  continue;
12918
- const line = stripLineComment2(rawLine);
13618
+ const line = stripLineComment3(rawLine);
12919
13619
  if (!line.trim())
12920
13620
  continue;
12921
13621
  const literals = literalsOf(line);
@@ -13027,7 +13727,7 @@ function blankBlockComments2(code) {
13027
13727
  from = close + 2;
13028
13728
  }
13029
13729
  }
13030
- function stripLineComment3(rawLine) {
13730
+ function stripLineComment4(rawLine) {
13031
13731
  if (rawLine.trimStart().startsWith("//"))
13032
13732
  return "";
13033
13733
  const at = rawLine.indexOf(" // ");
@@ -13038,7 +13738,7 @@ function logicalLines(code) {
13038
13738
  for (const raw of code.split("\n")) {
13039
13739
  if (raw.length > MAX_LINE_CHARS2)
13040
13740
  continue;
13041
- const line = stripLineComment3(raw).trimEnd();
13741
+ const line = stripLineComment4(raw).trimEnd();
13042
13742
  if (!line.trim())
13043
13743
  continue;
13044
13744
  const trimmed = line.trimStart();
@@ -13400,8 +14100,19 @@ function short4(text, max) {
13400
14100
  const flat = text.replace(/\s+/g, " ").trim();
13401
14101
  return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
13402
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
+ }
13403
14114
  function digitsOf(token) {
13404
- return token.replace(/[^\d.]/g, "").replace(/\.0+$|(\.\d*?)0+$/, "$1").replace(/^0+(?=\d)/, "");
14115
+ return stripFractionZeros(token.replace(/[^\d.]/g, "")).replace(/^0+(?=\d)/, "");
13405
14116
  }
13406
14117
  function computes(line) {
13407
14118
  const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX").replace(LITERAL_ARITH_RE, "N");
@@ -13619,7 +14330,7 @@ function findExpectCalls(code) {
13619
14330
  if (!subj)
13620
14331
  continue;
13621
14332
  const rest = code.slice(subj.end + 1, subj.end + 1 + 80);
13622
- 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);
13623
14334
  if (!mm)
13624
14335
  continue;
13625
14336
  const argStart = subj.end + 1 + mm[0].length;
@@ -15569,6 +16280,28 @@ function startsWithRegexLiteral(text) {
15569
16280
  const t = text.trimStart();
15570
16281
  return t[0] === "/" && t[1] !== "/" && t[1] !== "*" && regexLiteralEnd4(t, 0) !== -1;
15571
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
+ }
15572
16305
  function afterFirstArg(args) {
15573
16306
  let depth = 0;
15574
16307
  let quote = null;
@@ -15606,6 +16339,7 @@ function analyzeRegexMatchers(rawCode) {
15606
16339
  let examined = 0;
15607
16340
  let line = 1;
15608
16341
  let lineAt2 = 0;
16342
+ let regexNames;
15609
16343
  while ((m = re.exec(code)) !== null && examined++ < MAX_CALLS && out.length < MAX_REGEX_MATCHER_FINDINGS) {
15610
16344
  const subj = readBalanced(code, m.index + m[0].length);
15611
16345
  if (!subj)
@@ -15617,10 +16351,18 @@ function analyzeRegexMatchers(rawCode) {
15617
16351
  if (!args)
15618
16352
  continue;
15619
16353
  const expected = mm[2] === "toHaveJSProperty" ? afterFirstArg(args.text) : args.text;
15620
- if (!startsWithRegexLiteral(expected))
15621
- continue;
15622
- if (mm[2] !== "toHaveJSProperty" && mm[2] !== "toContain" && (startsWithRegexLiteral(subj.text) || /^new\s+RegExp\b/.test(subj.text.trim())))
16354
+ if (!isBareRegexLiteral(expected))
15623
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
+ }
15624
16366
  for (let i = code.indexOf("\n", lineAt2); i !== -1 && i < m.index; i = code.indexOf("\n", i + 1)) {
15625
16367
  line++;
15626
16368
  lineAt2 = i + 1;
@@ -15630,9 +16372,12 @@ function analyzeRegexMatchers(rawCode) {
15630
16372
  return out;
15631
16373
  }
15632
16374
  function formatRegexMatcherNote(findings) {
15633
- const lines = findings.map((f) => `Line ${f.line}: .${f.negated ? "not." : ""}${f.matcher}(\u2026 /regex/ \u2026) ${f.negated ? "always passes (a regex never equals the value), so it checks nothing" : "can never pass: this matcher compares by equality and a regex never equals a value"}.`);
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
+ });
15634
16379
  return `${lines.join("\n")}
15635
- A regex only works in toMatch, toHaveText, toContainText, toHaveValue and similar text matchers. For a DOM property read it first: expect(await loc.evaluate((n) => n.<prop>)).toMatch(/re/). Or give toHaveJSProperty the exact string (or boolean) the page shows. Keep every other line as-is.`;
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.`;
15636
16381
  }
15637
16382
  function lintTestQuality(rawCode, opts = {}) {
15638
16383
  if (!rawCode || typeof rawCode !== "string")
@@ -16381,19 +17126,6 @@ var init_fence_runs = __esm({
16381
17126
  }
16382
17127
  });
16383
17128
 
16384
- // ../shared/dist/trailing-slashes.js
16385
- function stripTrailingSlashes(value) {
16386
- let end = value.length;
16387
- while (end > 0 && value.charCodeAt(end - 1) === 47)
16388
- end--;
16389
- return end === value.length ? value : value.slice(0, end);
16390
- }
16391
- var init_trailing_slashes = __esm({
16392
- "../shared/dist/trailing-slashes.js"() {
16393
- "use strict";
16394
- }
16395
- });
16396
-
16397
17129
  // ../shared/dist/survey-bug-signature.js
16398
17130
  function normalizeWords(text) {
16399
17131
  return text.toLowerCase().replace(UUID, " id ").replace(PREFIXED_ID, " id ").replace(HEX_RUN, (run) => /\d/.test(run) ? " id " : run).replace(NUMBER_RUN, " # ").replace(/[^\p{L}\p{N}#]+/gu, " ").replace(/\s+/g, " ").trim();
@@ -16417,14 +17149,17 @@ function requestSignatures(text) {
16417
17149
  const statuses = [...text.matchAll(HTTP_STATUS)].map((m) => ({ status: m[1], at: m.index ?? 0, end: (m.index ?? 0) + m[0].length }));
16418
17150
  if (statuses.length === 0)
16419
17151
  return [];
16420
- const paths = [...text.matchAll(PATH_IN_TEXT)].map((m) => ({
16421
- method: m[1] ?? null,
16422
- path: normalizeBugPath((m[2] ?? m[3] ?? "").replace(/[.,;:]+$/, "")),
16423
- // Before normalisation, which turns a segment with a digit (favicon-32x32.png) into :id.
16424
- incidental: isIncidentalAssetPath((m[2] ?? m[3] ?? "").replace(/[.,;:]+$/, "").split(/[?#]/, 1)[0].toLowerCase()),
16425
- at: m.index ?? 0,
16426
- end: (m.index ?? 0) + m[0].length
16427
- })).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);
16428
17163
  const out = /* @__PURE__ */ new Map();
16429
17164
  for (const { status, at, end } of statuses) {
16430
17165
  const lead = STATUS_TO_PATH.exec(text.slice(end, end + 40));
@@ -16534,11 +17269,12 @@ function dedupeSiteWideSuspectedBugs(drafts) {
16534
17269
  delete plans.suspectedBugs;
16535
17270
  }
16536
17271
  }
16537
- 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;
16538
17273
  var init_survey_bug_signature = __esm({
16539
17274
  "../shared/dist/survey-bug-signature.js"() {
16540
17275
  "use strict";
16541
17276
  init_trailing_slashes();
17277
+ PATH_TRAILING_PUNCTUATION = /[.,;:]/;
16542
17278
  SIGNATURE_SOURCE_MAX_CHARS = 800;
16543
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;
16544
17280
  PREFIXED_ID = /\b[a-z]{1,8}[-_]\d+\b/g;
@@ -17869,7 +18605,7 @@ function pathIdsIn(text) {
17869
18605
  function findCounterIdPins(code) {
17870
18606
  if (code.length > CLOCK_MAX_CODE_CHARS)
17871
18607
  return [];
17872
- const clean2 = stripLineComments(blankBlockComments3(code));
18608
+ const clean3 = stripLineComments(blankBlockComments3(code));
17873
18609
  const fillValues = [];
17874
18610
  const candidates = [];
17875
18611
  let seenWrite = false;
@@ -17877,24 +18613,24 @@ function findCounterIdPins(code) {
17877
18613
  CALL_RE.lastIndex = 0;
17878
18614
  let m;
17879
18615
  let calls = 0;
17880
- while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean2)) !== null) {
18616
+ while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean3)) !== null) {
17881
18617
  const name = m[1];
17882
18618
  const open = m.index + m[0].length - 1;
17883
18619
  if (VALUE_WRITES.has(name)) {
17884
18620
  seenWrite = true;
17885
- const args2 = argsAt(clean2, open);
18621
+ const args2 = argsAt(clean3, open);
17886
18622
  if (args2 !== null)
17887
18623
  fillValues.push(...staticStrings(args2));
17888
18624
  continue;
17889
18625
  }
17890
18626
  if (createIndex === -1) {
17891
- if (name === "click" && seenWrite && isCreateClick(clean2, m.index))
18627
+ if (name === "click" && seenWrite && isCreateClick(clean3, m.index))
17892
18628
  createIndex = m.index;
17893
18629
  continue;
17894
18630
  }
17895
18631
  if (name === "click")
17896
18632
  continue;
17897
- const args = argsAt(clean2, open);
18633
+ const args = argsAt(clean3, open);
17898
18634
  if (args === null)
17899
18635
  continue;
17900
18636
  const isUrl = URL_SINKS.has(name);
@@ -17913,7 +18649,7 @@ function findCounterIdPins(code) {
17913
18649
  }
17914
18650
  if (createIndex === -1)
17915
18651
  return [];
17916
- const before = clean2.slice(0, createIndex);
18652
+ const before = clean3.slice(0, createIndex);
17917
18653
  let pathIdsBefore = null;
17918
18654
  const findings = [];
17919
18655
  const seen = /* @__PURE__ */ new Set();
@@ -17937,7 +18673,7 @@ function findCounterIdPins(code) {
17937
18673
  continue;
17938
18674
  }
17939
18675
  }
17940
- 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) });
17941
18677
  }
17942
18678
  return findings;
17943
18679
  }
@@ -18232,7 +18968,7 @@ var init_suspicious_expectation = __esm({
18232
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;
18233
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;
18234
18970
  STUCK_VETO_RE = new RegExp(`${NOT_STUCK_RE.source}|${LISTS_STUCK_RE.source}`, "i");
18235
- 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;
18236
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)";
18237
18973
  ELLIPSIS = "(?:\\.{3}|\u2026)";
18238
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;
@@ -18388,7 +19124,7 @@ function outcomeNamedByAssertion(outcome, code) {
18388
19124
  function namedByStatements(outcome, statements) {
18389
19125
  const text = outcomeText(outcome);
18390
19126
  if (outcome.type === "url") {
18391
- 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);
18392
19128
  return statements.some((stmt) => {
18393
19129
  if (!/\.(?:waitForURL|toHaveURL)\s{0,20}\(/.test(stmt))
18394
19130
  return false;
@@ -18560,7 +19296,7 @@ function evaluatePromotionGate(input, checks = PROMOTION_QUALITY_CHECKS) {
18560
19296
  ...suspiciousTagged ? { suspiciousPass: { mode: suspicious, covered: suspiciousCovered, wouldHold: suspiciousHold } } : {}
18561
19297
  };
18562
19298
  }
18563
- 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;
18564
19300
  var init_promotion_quality_gate = __esm({
18565
19301
  "../shared/dist/promotion-quality-gate.js"() {
18566
19302
  "use strict";
@@ -18575,11 +19311,12 @@ var init_promotion_quality_gate = __esm({
18575
19311
  init_run_classification();
18576
19312
  init_expected_outcome_coverage();
18577
19313
  init_expect_call();
19314
+ init_trailing_slashes();
18578
19315
  SOURCE_RANK = { generator: 0, classifier: 0, healer: 1, human: 2 };
18579
19316
  strengthCheck = {
18580
19317
  id: "assertion-strength",
18581
- run({ code, tags, strengthGate, flagContradictedAdditions }) {
18582
- const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true });
19318
+ run({ code, tags, strengthGate, flagContradictedAdditions, lastActionProof }) {
19319
+ const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true, lastActionProof });
18583
19320
  if (verdict.severity === "route-bug")
18584
19321
  return [{ check: "route-bug" }];
18585
19322
  const authDowngrade = verdict.severity === "downgrade" && tags.some((t) => t.toLowerCase() === "@auth");
@@ -18597,7 +19334,7 @@ var init_promotion_quality_gate = __esm({
18597
19334
  MAX_STATEMENTS = 500;
18598
19335
  MAX_STATEMENT_CHARS = 400;
18599
19336
  LITERAL_OR_IDENTIFIER = /'(?:[^'\\\n]|\\.){0,500}'|"(?:[^"\\\n]|\\.){0,500}"|`(?:[^`\\]|\\.){0,2000}`|(?<![\w$.])[A-Za-z_$][\w$]{0,60}/g;
18600
- 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}\(/;
18601
19338
  FEEDBACK_REGION = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/i;
18602
19339
  ONE_SHOT_MESSAGE_REASON = "one-shot success/error banner text";
18603
19340
  FEEDBACK_REGION_ALL = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/gi;
@@ -18656,6 +19393,7 @@ var init_promotion_quality_gate = __esm({
18656
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}\)/
18657
19394
  };
18658
19395
  ROUTE_TERM = /\/[A-Za-z0-9_./?&=#-]{1,200}/g;
19396
+ ROUTE_TRAILING_PUNCTUATION = /[.,;:!?)]/;
18659
19397
  QUOTED_LITERAL = /'([^'\\\n]{3,200})'|"([^"\\\n]{3,200})"|`([^`\\$\n]{3,200})`/g;
18660
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;
18661
19399
  MAX_OUTCOME_STATEMENTS = 300;
@@ -18775,8 +19513,8 @@ ${goalText}`);
18775
19513
  return count3 > 0 ? [{ check: "locator-fragment", detail: String(count3), soft: true }] : [];
18776
19514
  }
18777
19515
  };
18778
- 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;
18779
- 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*\(/;
18780
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;
18781
19519
  MAX_FILTER_LINES = 2e3;
18782
19520
  MAX_FILTER_LINE_CHARS = 600;
@@ -18891,7 +19629,7 @@ ${goalText}`);
18891
19629
  });
18892
19630
 
18893
19631
  // ../shared/dist/regex-alternation.js
18894
- function closingParens(source) {
19632
+ function closingParens2(source) {
18895
19633
  const close = new Int32Array(source.length).fill(-1);
18896
19634
  const open = [];
18897
19635
  let inClass = false;
@@ -18916,7 +19654,7 @@ function closingParens(source) {
18916
19654
  return close;
18917
19655
  }
18918
19656
  function alternatesAtTop(source) {
18919
- const close = closingParens(source);
19657
+ const close = closingParens2(source);
18920
19658
  let start = source.startsWith("^") ? 1 : 0;
18921
19659
  let end = source.endsWith("$") ? source.length - 1 : source.length;
18922
19660
  while (start < end && source[start] === "(" && close[start] === end - 1) {
@@ -18965,6 +19703,124 @@ var init_page_evidence = __esm({
18965
19703
  }
18966
19704
  });
18967
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
+
18968
19824
  // ../shared/dist/index.js
18969
19825
  var dist_exports = {};
18970
19826
  __export(dist_exports, {
@@ -18972,9 +19828,11 @@ __export(dist_exports, {
18972
19828
  ACTION_BATCH_MAX_SIZE: () => ACTION_BATCH_MAX_SIZE,
18973
19829
  ADDON_DEFINITIONS: () => ADDON_DEFINITIONS,
18974
19830
  ADMIN_EMAIL_LIMITS: () => ADMIN_EMAIL_LIMITS,
19831
+ ADMIN_FORCE_CANCELLED_RUN_ERROR: () => ADMIN_FORCE_CANCELLED_RUN_ERROR,
18975
19832
  ADMIN_VIEW_DEFAULT_RETURN_TO: () => ADMIN_VIEW_DEFAULT_RETURN_TO,
18976
19833
  AGENT_AI_PROVIDER_PREFIXES: () => AGENT_AI_PROVIDER_PREFIXES,
18977
19834
  AI_CREDIT_MARGIN: () => AI_CREDIT_MARGIN,
19835
+ ALLOWED_PROVIDER_CHAIN_HOPS: () => ALLOWED_PROVIDER_CHAIN_HOPS,
18978
19836
  API_AUTH_HEADERS_ENV_KEY: () => API_AUTH_HEADERS_ENV_KEY,
18979
19837
  API_MODEL_CATALOG: () => API_MODEL_CATALOG,
18980
19838
  API_MODEL_FIELDS: () => API_MODEL_FIELDS,
@@ -19014,12 +19872,14 @@ __export(dist_exports, {
19014
19872
  BUG_TEST_BROKEN_REASON: () => BUG_TEST_BROKEN_REASON,
19015
19873
  BUG_TEST_BROKEN_TAG: () => BUG_TEST_BROKEN_TAG,
19016
19874
  CACHE_PRICE_PER_M: () => CACHE_PRICE_PER_M,
19875
+ CANCELLED_REASON_PREFIXES: () => CANCELLED_REASON_PREFIXES,
19017
19876
  CANVAS_UNSUPPORTED_BODY: () => CANVAS_UNSUPPORTED_BODY,
19018
19877
  CANVAS_UNSUPPORTED_CODE: () => CANVAS_UNSUPPORTED_CODE,
19019
19878
  CANVAS_UNSUPPORTED_NOT_CHARGED: () => CANVAS_UNSUPPORTED_NOT_CHARGED,
19020
19879
  CANVAS_UNSUPPORTED_OVERRIDE_HINT: () => CANVAS_UNSUPPORTED_OVERRIDE_HINT,
19021
19880
  CANVAS_UNSUPPORTED_TITLE: () => CANVAS_UNSUPPORTED_TITLE,
19022
19881
  CAPTURE_JSON_STRUCTURE_LIMITS: () => CAPTURE_JSON_STRUCTURE_LIMITS,
19882
+ CHAIN_CANCELLED_RUN_ERROR: () => CHAIN_CANCELLED_RUN_ERROR,
19023
19883
  CHAT_FAILOVER_KEY: () => CHAT_FAILOVER_KEY,
19024
19884
  CLAUDE_CLI_DEFAULT_EFFORT: () => CLAUDE_CLI_DEFAULT_EFFORT,
19025
19885
  CLAUDE_CLI_DEFAULT_MODEL: () => CLAUDE_CLI_DEFAULT_MODEL,
@@ -19056,6 +19916,7 @@ __export(dist_exports, {
19056
19916
  DEFAULT_GEMINI_SCREEN_MODEL: () => DEFAULT_GEMINI_SCREEN_MODEL,
19057
19917
  DEFAULT_JSON_STRUCTURE_LIMITS: () => DEFAULT_JSON_STRUCTURE_LIMITS,
19058
19918
  DEFAULT_PLATFORM_DB_POOL_SIZE: () => DEFAULT_PLATFORM_DB_POOL_SIZE,
19919
+ DEFAULT_PROVIDER_ACCOUNT_ID: () => DEFAULT_PROVIDER_ACCOUNT_ID,
19059
19920
  DEFAULT_SHARED_DB_POOL_SIZE: () => DEFAULT_SHARED_DB_POOL_SIZE,
19060
19921
  DEFAULT_STT_MODEL: () => DEFAULT_STT_MODEL,
19061
19922
  DEFAULT_STT_MODEL_BY_PROVIDER: () => DEFAULT_STT_MODEL_BY_PROVIDER,
@@ -19072,6 +19933,7 @@ __export(dist_exports, {
19072
19933
  ENVIRONMENTAL_ERROR_STORED_PREFIXES: () => ENVIRONMENTAL_ERROR_STORED_PREFIXES,
19073
19934
  EVIDENCE_NOTE_PREFIXES: () => EVIDENCE_NOTE_PREFIXES,
19074
19935
  EXPECT_CALL_RE: () => EXPECT_CALL_RE,
19936
+ FEATURE_ROW_RUN_WINDOW: () => FEATURE_ROW_RUN_WINDOW,
19075
19937
  FLOW_PHASE_LABELS: () => FLOW_PHASE_LABELS,
19076
19938
  FLOW_PHASE_TYPES: () => FLOW_PHASE_TYPES,
19077
19939
  FLOW_SERVER_TYPES: () => FLOW_SERVER_TYPES,
@@ -19139,6 +20001,8 @@ __export(dist_exports, {
19139
20001
  MAX_ORG_NAME_LENGTH: () => MAX_ORG_NAME_LENGTH,
19140
20002
  MAX_PASSWORD_BYTES: () => MAX_PASSWORD_BYTES,
19141
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,
19142
20006
  MAX_SURVEY_ENTRY_URLS: () => MAX_SURVEY_ENTRY_URLS,
19143
20007
  MAX_SURVEY_ENTRY_URL_LENGTH: () => MAX_SURVEY_ENTRY_URL_LENGTH,
19144
20008
  MCP_ARMY_LIMITS: () => MCP_ARMY_LIMITS,
@@ -19174,7 +20038,7 @@ __export(dist_exports, {
19174
20038
  MUSE_ENGINE_DEFAULT_MODEL: () => MUSE_ENGINE_DEFAULT_MODEL,
19175
20039
  MUSE_ENGINE_MODELS: () => MUSE_ENGINE_MODELS,
19176
20040
  NOREPLY_EMAIL: () => NOREPLY_EMAIL,
19177
- OPENCODE_GO_2_PREFIX: () => OPENCODE_GO_2_PREFIX,
20041
+ OPENCODE_GO_DEEPSEEK_GLM_MODELS: () => OPENCODE_GO_DEEPSEEK_GLM_MODELS,
19178
20042
  OPENCODE_GO_MODEL_PATTERN: () => OPENCODE_GO_MODEL_PATTERN,
19179
20043
  OPENCODE_GO_PREFIX: () => OPENCODE_GO_PREFIX,
19180
20044
  OPENROUTER_MAX_CALL_COST_USD: () => OPENROUTER_MAX_CALL_COST_USD,
@@ -19194,10 +20058,12 @@ __export(dist_exports, {
19194
20058
  PLAN_FEATURES: () => PLAN_FEATURES,
19195
20059
  PLAN_FEATURE_LOCKED_CODE: () => PLAN_FEATURE_LOCKED_CODE,
19196
20060
  PLAN_ORDER: () => PLAN_ORDER,
20061
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR: () => PLATFORM_ADMIN_CANCELLED_RUN_ERROR,
19197
20062
  PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
19198
20063
  PLATFORM_DEFAULT_PROVIDER: () => PLATFORM_DEFAULT_PROVIDER,
19199
20064
  PLATFORM_PLAN_BILLING: () => PLATFORM_PLAN_BILLING,
19200
20065
  PLATFORM_SERVER_APPLICATION_NAME: () => PLATFORM_SERVER_APPLICATION_NAME,
20066
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR: () => PLATFORM_SUPPORT_CANCELLED_RUN_ERROR,
19201
20067
  POST_SPECIAL_TRIAL_DAYS: () => POST_SPECIAL_TRIAL_DAYS,
19202
20068
  PRESET_DIRECT_FALLBACK: () => PRESET_DIRECT_FALLBACK,
19203
20069
  PRODUCTION_READ_ONLY_GUARD_TEMPLATE: () => PRODUCTION_READ_ONLY_GUARD_TEMPLATE,
@@ -19209,8 +20075,12 @@ __export(dist_exports, {
19209
20075
  PROJECT_OVERRIDE_FIELDS: () => PROJECT_OVERRIDE_FIELDS,
19210
20076
  PROMOTION_GATE_MAX_CODE_CHARS: () => PROMOTION_GATE_MAX_CODE_CHARS,
19211
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,
19212
20080
  PROVIDER_CHAINS: () => PROVIDER_CHAINS,
20081
+ PROVIDER_CHAINS_SETTING_MAX_CHARS: () => PROVIDER_CHAINS_SETTING_MAX_CHARS,
19213
20082
  PROVIDER_CHAIN_LEADS: () => PROVIDER_CHAIN_LEADS,
20083
+ PROVIDER_CHAIN_MAX_ENTRIES: () => PROVIDER_CHAIN_MAX_ENTRIES,
19214
20084
  PROVIDER_HOST_NOT_OFFERED: () => PROVIDER_HOST_NOT_OFFERED,
19215
20085
  PUBLIC_PLANS: () => PUBLIC_PLANS,
19216
20086
  QUARANTINE_CLOSE_TAGS: () => QUARANTINE_CLOSE_TAGS,
@@ -19218,10 +20088,12 @@ __export(dist_exports, {
19218
20088
  READ_ONLY_TAGS: () => READ_ONLY_TAGS,
19219
20089
  RECHECKED_NOTES: () => RECHECKED_NOTES,
19220
20090
  RECORDING_VALUE_PLACEHOLDERS: () => RECORDING_VALUE_PLACEHOLDERS,
20091
+ RENDER_IDENTITY_MAX_LENGTH: () => RENDER_IDENTITY_MAX_LENGTH,
19221
20092
  REPOSITORY_CLIS: () => REPOSITORY_CLIS,
19222
20093
  RESERVED_PROJECT_KEYS: () => RESERVED_PROJECT_KEYS,
19223
20094
  RESULT_STRUCTURE_TOO_COSTLY_CODE: () => RESULT_STRUCTURE_TOO_COSTLY_CODE,
19224
20095
  RETRIED_ATTEMPTS_KEY: () => RETRIED_ATTEMPTS_KEY,
20096
+ RETRY_HELD_RUN_ERROR: () => RETRY_HELD_RUN_ERROR,
19225
20097
  RUNNER_INTERRUPT_ERROR_PREFIX: () => RUNNER_INTERRUPT_ERROR_PREFIX,
19226
20098
  RUN_INTERRUPTED_ERROR_PREFIXES: () => RUN_INTERRUPTED_ERROR_PREFIXES,
19227
20099
  RUN_INTERRUPTION_NOT_RETRIED_MARKERS: () => RUN_INTERRUPTION_NOT_RETRIED_MARKERS,
@@ -19234,6 +20106,8 @@ __export(dist_exports, {
19234
20106
  SHARED_SERVER_APPLICATION_NAME: () => SHARED_SERVER_APPLICATION_NAME,
19235
20107
  SPEND_SOURCE_KEY: () => SPEND_SOURCE_KEY,
19236
20108
  SPEND_SOURCE_SERVER: () => SPEND_SOURCE_SERVER,
20109
+ STATED_LIMITS_VERSION: () => STATED_LIMITS_VERSION,
20110
+ STATED_PATTERN_MAX: () => STATED_PATTERN_MAX,
19237
20111
  STATIC_STANDARD_MIN_INTERACTIVE: () => STATIC_STANDARD_MIN_INTERACTIVE,
19238
20112
  STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
19239
20113
  SUCCESS_NOTICE_TEXT: () => SUCCESS_NOTICE_TEXT,
@@ -19263,14 +20137,20 @@ __export(dist_exports, {
19263
20137
  UI_VARIANTS: () => UI_VARIANTS,
19264
20138
  UNEXPECTED_PASS_ERROR_PREFIX: () => UNEXPECTED_PASS_ERROR_PREFIX,
19265
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,
19266
20144
  VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
19267
20145
  WEB_PROXY_CHAIN_HEADER: () => WEB_PROXY_CHAIN_HEADER,
19268
20146
  WITHHELD_CREDENTIAL_TEXT: () => WITHHELD_CREDENTIAL_TEXT,
19269
20147
  ZAI_MODEL_PATTERN: () => ZAI_MODEL_PATTERN,
19270
20148
  ZAI_PREFIX: () => ZAI_PREFIX,
20149
+ accountServesModel: () => accountServesModel,
19271
20150
  accumulateStatements: () => accumulateStatements,
19272
20151
  adoptBrokerFailover: () => adoptBrokerFailover,
19273
20152
  aiCostToCreditCents: () => aiCostToCreditCents,
20153
+ allowedProviderChainHosts: () => allowedProviderChainHosts,
19274
20154
  alternatesAtTop: () => alternatesAtTop,
19275
20155
  analyzeDerivedValues: () => analyzeDerivedValues,
19276
20156
  analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
@@ -19283,6 +20163,7 @@ __export(dist_exports, {
19283
20163
  analyzeUnreachedUrlWaits: () => analyzeUnreachedUrlWaits,
19284
20164
  analyzeUrlWaits: () => analyzeUrlWaits,
19285
20165
  analyzeWriteResponse: () => analyzeWriteResponse,
20166
+ androidResourceOwners: () => androidResourceOwners,
19286
20167
  apiModelCardsFromConfig: () => apiModelCardsFromConfig,
19287
20168
  apiModelsForProvider: () => apiModelsForProvider,
19288
20169
  apiProviderModelLabel: () => apiProviderModelLabel,
@@ -19293,6 +20174,7 @@ __export(dist_exports, {
19293
20174
  applyLaunchSpecialPlan: () => applyLaunchSpecialPlan,
19294
20175
  asPlainExpect: () => asPlainExpect,
19295
20176
  assertionOnlyCode: () => assertionOnlyCode,
20177
+ assertionStrengthHoistedReadsOn: () => assertionStrengthHoistedReadsOn,
19296
20178
  assertionsReadVisibleContent: () => assertionsReadVisibleContent,
19297
20179
  assessCodeOutcomeCoverage: () => assessCodeOutcomeCoverage,
19298
20180
  assessTitleCoverage: () => assessTitleCoverage,
@@ -19376,6 +20258,7 @@ __export(dist_exports, {
19376
20258
  deepgramSpeak: () => deepgramSpeak,
19377
20259
  deepgramTranscribe: () => deepgramTranscribe,
19378
20260
  defaultConfirmModel: () => defaultConfirmModel,
20261
+ defaultProviderChainEntries: () => defaultProviderChainEntries,
19379
20262
  defaultScreenModel: () => defaultScreenModel,
19380
20263
  defaultVoiceForTtsModel: () => defaultVoiceForTtsModel,
19381
20264
  deriveAuthPlan: () => deriveAuthPlan,
@@ -19418,6 +20301,7 @@ __export(dist_exports, {
19418
20301
  extractOutcomeSearchTerms: () => extractOutcomeSearchTerms,
19419
20302
  featureForAiCallType: () => featureForAiCallType,
19420
20303
  featureForMcpTool: () => featureForMcpTool,
20304
+ featureRowRunStatus: () => featureRowRunStatus,
19421
20305
  findBracedClause: () => findBracedClause,
19422
20306
  findCounterIdPins: () => findCounterIdPins,
19423
20307
  findCredentialTextLeftover: () => findCredentialTextLeftover,
@@ -19466,6 +20350,7 @@ __export(dist_exports, {
19466
20350
  getPlanChangeType: () => getPlanChangeType,
19467
20351
  getPlanDefinition: () => getPlanDefinition,
19468
20352
  getPlanLabel: () => getPlanLabel,
20353
+ getProviderChainOverrides: () => getProviderChainOverrides,
19469
20354
  getTrialDaysRemaining: () => getTrialDaysRemaining,
19470
20355
  googleBrokerNavigationUrl: () => googleBrokerNavigationUrl,
19471
20356
  googleBrokerUnavailableLine: () => googleBrokerUnavailableLine,
@@ -19479,10 +20364,10 @@ __export(dist_exports, {
19479
20364
  hasEncodedRequestBody: () => hasEncodedRequestBody,
19480
20365
  hasEngineSpecParts: () => hasEngineSpecParts,
19481
20366
  hasOnlyConstantAssertions: () => hasOnlyConstantAssertions,
19482
- hasOpenCodeGo2Prefix: () => hasOpenCodeGo2Prefix,
19483
20367
  hasOpenCodeGoPrefix: () => hasOpenCodeGoPrefix,
19484
20368
  hasOpenRouterPrefix: () => hasOpenRouterPrefix,
19485
20369
  hasPlatformLocator: () => hasPlatformLocator,
20370
+ hasProviderChainLeadsByDefault: () => hasProviderChainLeadsByDefault,
19486
20371
  hasReadOnlyMarker: () => hasReadOnlyMarker,
19487
20372
  hasSurveyNotes: () => hasSurveyNotes,
19488
20373
  hasTestFailContract: () => hasTestFailContract,
@@ -19508,8 +20393,11 @@ __export(dist_exports, {
19508
20393
  isAuthTokenName: () => isAuthTokenName,
19509
20394
  isBlockedEnvVar: () => isBlockedEnvVar,
19510
20395
  isBrowserCliEngine: () => isBrowserCliEngine,
20396
+ isBrowserVersion: () => isBrowserVersion,
19511
20397
  isBugTestBrokenError: () => isBugTestBrokenError,
20398
+ isCancelledReason: () => isCancelledReason,
19512
20399
  isCanvasUnsupportedMessage: () => isCanvasUnsupportedMessage,
20400
+ isChainablePrimary: () => isChainablePrimary,
19513
20401
  isCliEngineModelId: () => isCliEngineModelId,
19514
20402
  isCliEngineSpec: () => isCliEngineSpec,
19515
20403
  isCodeFactNote: () => isCodeFactNote,
@@ -19533,6 +20421,7 @@ __export(dist_exports, {
19533
20421
  isIdentityMobileXpath: () => isIdentityMobileXpath,
19534
20422
  isIdentityProviderSurface: () => isIdentityProviderSurface,
19535
20423
  isIncidentalAssetPath: () => isIncidentalAssetPath,
20424
+ isIntentionalNegativeAuthTest: () => isIntentionalNegativeAuthTest,
19536
20425
  isIpOrLocalhost: () => isIpOrLocalhost,
19537
20426
  isKnownPageType: () => isKnownPageType,
19538
20427
  isLaunchSpecialActive: () => isLaunchSpecialActive,
@@ -19560,6 +20449,7 @@ __export(dist_exports, {
19560
20449
  isRunInterruptedError: () => isRunInterruptedError,
19561
20450
  isRunnableStep: () => isRunnableStep,
19562
20451
  isSafeDeepLink: () => isSafeDeepLink,
20452
+ isSafeMaskSelector: () => isSafeMaskSelector,
19563
20453
  isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
19564
20454
  isSelectableApiModel: () => isSelectableApiModel,
19565
20455
  isSelfServePlan: () => isSelfServePlan,
@@ -19623,6 +20513,7 @@ __export(dist_exports, {
19623
20513
  normalizeFinishReason: () => normalizeFinishReason,
19624
20514
  normalizeNoteText: () => normalizeNoteText,
19625
20515
  normalizePageType: () => normalizePageType,
20516
+ normalizeProviderAccountModels: () => normalizeProviderAccountModels,
19626
20517
  normalizeTtsModel: () => normalizeTtsModel,
19627
20518
  normalizeTtsVoiceId: () => normalizeTtsVoiceId,
19628
20519
  noteChatFailover: () => noteChatFailover,
@@ -19647,13 +20538,18 @@ __export(dist_exports, {
19647
20538
  parseGeminiModelsPayload: () => parseGeminiModelsPayload,
19648
20539
  parseGoogleAuthorizeUrl: () => parseGoogleAuthorizeUrl,
19649
20540
  parseGoogleGisRequestUrl: () => parseGoogleGisRequestUrl,
20541
+ parseLastActionProofMode: () => parseLastActionProofMode,
19650
20542
  parseLimitParam: () => parseLimitParam,
19651
20543
  parseOffsetParam: () => parseOffsetParam,
19652
20544
  parsePageParam: () => parsePageParam,
19653
20545
  parsePgIntSetting: () => parsePgIntSetting,
19654
20546
  parseProjectEngineOverrides: () => parseProjectEngineOverrides,
19655
20547
  parsePromotionGateMode: () => parsePromotionGateMode,
20548
+ parseProviderAccountLists: () => parseProviderAccountLists,
20549
+ parseProviderChainOverrides: () => parseProviderChainOverrides,
19656
20550
  parseRecheckClearMode: () => parseRecheckClearMode,
20551
+ parseRenderIdentity: () => parseRenderIdentity,
20552
+ parseStoredProviderChainOverrides: () => parseStoredProviderChainOverrides,
19657
20553
  parseSurveyEntryUrls: () => parseSurveyEntryUrls,
19658
20554
  parseSuspiciousPassMode: () => parseSuspiciousPassMode,
19659
20555
  parseTaskModelUpdate: () => parseTaskModelUpdate,
@@ -19684,7 +20580,9 @@ __export(dist_exports, {
19684
20580
  projectAppHosts: () => projectAppHosts,
19685
20581
  providerChainFor: () => providerChainFor,
19686
20582
  providerChainHostOf: () => providerChainHostOf,
20583
+ providerChainHostProvider: () => providerChainHostProvider,
19687
20584
  providerChainLeadsFor: () => providerChainLeadsFor,
20585
+ providerChainModelKey: () => providerChainModelKey,
19688
20586
  providerChainPrimariesForHop: () => providerChainPrimariesForHop,
19689
20587
  providerHostPlan: () => providerHostPlan,
19690
20588
  readBalanced: () => readBalanced,
@@ -19694,6 +20592,7 @@ __export(dist_exports, {
19694
20592
  readEnabledEngines: () => readEnabledEngines,
19695
20593
  readProjectEngineOverrides: () => readProjectEngineOverrides,
19696
20594
  readRetriedAttempts: () => readRetriedAttempts,
20595
+ readStatedLimits: () => readStatedLimits,
19697
20596
  readStaticPageCanvasSignals: () => readStaticPageCanvasSignals,
19698
20597
  readSurveyEntryUrls: () => readSurveyEntryUrls,
19699
20598
  readSurveyNotes: () => readSurveyNotes,
@@ -19706,6 +20605,7 @@ __export(dist_exports, {
19706
20605
  regexLiteralEnd: () => regexLiteralEnd2,
19707
20606
  regexOpensAfter: () => regexOpensAfter,
19708
20607
  registrableDomain: () => registrableDomain,
20608
+ renderIdentity: () => renderIdentity,
19709
20609
  renderSurveyNotesForPrompt: () => renderSurveyNotesForPrompt,
19710
20610
  repairPageType: () => repairPageType,
19711
20611
  replaceBlockComments: () => replaceBlockComments,
@@ -19753,11 +20653,14 @@ __export(dist_exports, {
19753
20653
  selectorCandidatesForStep: () => selectorCandidatesForStep,
19754
20654
  selectorForValue: () => selectorForValue,
19755
20655
  setAgenticCliRuntime: () => setAgenticCliRuntime,
20656
+ setProviderChainOverrides: () => setProviderChainOverrides,
19756
20657
  signupPlanFor: () => signupPlanFor,
19757
20658
  simplifyMobileWanderingInPlace: () => simplifyMobileWanderingInPlace,
19758
20659
  siteOf: () => siteOf,
19759
20660
  specItemsFromTestPlans: () => specItemsFromTestPlans,
19760
20661
  splitAnswerLines: () => splitAnswerLines,
20662
+ statedLimitsFromAttrs: () => statedLimitsFromAttrs,
20663
+ statedLimitsRecord: () => statedLimitsRecord,
19761
20664
  stepAppliesToPlatform: () => stepAppliesToPlatform,
19762
20665
  stepConsoleLines: () => stepConsoleLines,
19763
20666
  stepIsDivergentForPlatform: () => stepIsDivergentForPlatform,
@@ -19769,7 +20672,10 @@ __export(dist_exports, {
19769
20672
  stripCodeFactPrefix: () => stripCodeFactPrefix,
19770
20673
  stripCoverageComments: () => stripCoverageComments,
19771
20674
  stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
20675
+ stripFractionZeros: () => stripFractionZeros,
20676
+ stripLineComment: () => stripLineComment,
19772
20677
  stripLoginScaffolding: () => stripLoginScaffolding,
20678
+ stripTrailingChars: () => stripTrailingChars,
19773
20679
  stripTrailingSlashes: () => stripTrailingSlashes,
19774
20680
  strongerNeedsReviewSource: () => strongerNeedsReviewSource,
19775
20681
  sttModelsForProvider: () => sttModelsForProvider,
@@ -19787,10 +20693,14 @@ __export(dist_exports, {
19787
20693
  ttsFallbackModel: () => ttsFallbackModel,
19788
20694
  ttsProviderForModel: () => ttsProviderForModel,
19789
20695
  unexpectedPassError: () => unexpectedPassError,
20696
+ v1RenderIdentity: () => v1RenderIdentity,
19790
20697
  validateAppMapDocument: () => validateAppMapDocument,
19791
20698
  validateAssertionStrength: () => validateAssertionStrength,
19792
20699
  validateEngineSpec: () => validateEngineSpec,
20700
+ validateProviderChainEntries: () => validateProviderChainEntries,
20701
+ validateProviderChainsSetting: () => validateProviderChainsSetting,
19793
20702
  validateTaskModelId: () => validateTaskModelId,
20703
+ visualBaselinesEnabled: () => visualBaselinesEnabled,
19794
20704
  visualProDailyBudget: () => visualProDailyBudget,
19795
20705
  voiceMatchesTtsModel: () => voiceMatchesTtsModel,
19796
20706
  voicesForTtsModel: () => voicesForTtsModel,
@@ -19853,11 +20763,14 @@ var init_dist = __esm({
19853
20763
  init_deepgram_voices();
19854
20764
  init_feature_flags();
19855
20765
  init_env_flags();
20766
+ init_visual_compare_limits();
20767
+ init_visual_baseline_identity();
19856
20768
  init_analytics_url();
19857
20769
  init_agentic_cli_runtime();
19858
20770
  init_api_model_catalog();
19859
20771
  init_engine_spec();
19860
20772
  init_provider_chains();
20773
+ init_provider_accounts();
19861
20774
  init_project_engine_overrides();
19862
20775
  init_ai_profiles();
19863
20776
  init_openrouter_usage();
@@ -19899,6 +20812,7 @@ var init_dist = __esm({
19899
20812
  init_regex_alternation();
19900
20813
  init_page_evidence();
19901
20814
  init_trailing_slashes();
20815
+ init_stated_limits();
19902
20816
  }
19903
20817
  });
19904
20818
 
@@ -20037,7 +20951,7 @@ var require_repo_brief = __commonJS({
20037
20951
  const signupJobs = brief.jobs.filter((job) => /sign.?up|register|create account/i.test(`${job.name} ${job.entry}`));
20038
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}`));
20039
20953
  }
20040
- 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"]);
20041
20955
  var SESSIONS = /* @__PURE__ */ new Set(["none", "optional", "required"]);
20042
20956
  var FACT_KINDS = /* @__PURE__ */ new Set(["workflow", "business_rule", "state_transition", "permission", "integration", "data_model"]);
20043
20957
  var MISSING_REASON = "cited by the reader without a reason";
@@ -20150,7 +21064,7 @@ var require_repo_brief = __commonJS({
20150
21064
  const scenarioRefs = refs(scenario.sourceRefs);
20151
21065
  if (typeof scenarioName !== "string" || typeof expected !== "string" || scenarioRefs.length === 0)
20152
21066
  continue;
20153
- if (typeof kind !== "string" || !KINDS.has(kind))
21067
+ if (typeof kind !== "string" || !KINDS2.has(kind))
20154
21068
  continue;
20155
21069
  scenarios.push({
20156
21070
  name: scenarioName.trim().slice(0, 160),
@@ -20350,7 +21264,7 @@ var require_route_manifest = __commonJS({
20350
21264
  return `routes-${n}.json`;
20351
21265
  }
20352
21266
  var AUTH_HINTS = /* @__PURE__ */ new Set(["public", "auth", "unknown"]);
20353
- var KINDS = /* @__PURE__ */ new Set(["page", "api", "redirect"]);
21267
+ var KINDS2 = /* @__PURE__ */ new Set(["page", "api", "redirect"]);
20354
21268
  var COMPLETENESS = /* @__PURE__ */ new Set(["complete", "partial", "unsupported"]);
20355
21269
  var PROVENANCES = /* @__PURE__ */ new Set(["cli", "file-convention", "route-config"]);
20356
21270
  function str(value, max) {
@@ -20457,7 +21371,7 @@ var require_route_manifest = __commonJS({
20457
21371
  if (seenFamilies.has(family)) {
20458
21372
  const kept = routes.find((r2) => manifestRouteFamilyKey(r2.concretePath ?? r2.pattern ?? "") === family);
20459
21373
  const more = strList(rr.methods, 8, 10).map((m) => m.toUpperCase()).filter((m) => /^[A-Z]{3,10}$/.test(m));
20460
- 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";
20461
21375
  if (kept?.kind === "api" && rowKind === "api" && more.length) {
20462
21376
  kept.methods = [.../* @__PURE__ */ new Set([...kept.methods ?? [], ...more])].slice(0, 8);
20463
21377
  } else {
@@ -20471,7 +21385,7 @@ var require_route_manifest = __commonJS({
20471
21385
  }
20472
21386
  seenFamilies.add(family);
20473
21387
  index += 1;
20474
- 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";
20475
21389
  const authHint = AUTH_HINTS.has(rr.authHint) ? rr.authHint : "unknown";
20476
21390
  const methods = strList(rr.methods, 8, 10).map((m) => m.toUpperCase()).filter((m) => /^[A-Z]{3,10}$/.test(m));
20477
21391
  const sourceCheck = rr.sourceCheck === "matched" || rr.sourceCheck === "unmatched" ? rr.sourceCheck : void 0;
@@ -168434,7 +169348,7 @@ ${lanes.join("\n")}
168434
169348
  startWatching(state, buildOrder);
168435
169349
  return isCircularBuildOrder(buildOrder) ? 4 : !buildOrder.some((p) => state.diagnostics.has(toResolvedConfigFilePath(state, p))) ? 0 : successfulProjects ? 2 : 1;
168436
169350
  }
168437
- function clean2(state, project, onlyReferences) {
169351
+ function clean3(state, project, onlyReferences) {
168438
169352
  mark("SolutionBuilder::beforeClean");
168439
169353
  const result = cleanWorker(state, project, onlyReferences);
168440
169354
  mark("SolutionBuilder::afterClean");
@@ -168723,7 +169637,7 @@ ${lanes.join("\n")}
168723
169637
  const state = createSolutionBuilderState(watch, hostOrHostWithWatch, rootNames, options, baseWatchOptions);
168724
169638
  return {
168725
169639
  build: (project, cancellationToken, writeFile2, getCustomTransformers) => build(state, project, cancellationToken, writeFile2, getCustomTransformers),
168726
- clean: (project) => clean2(state, project),
169640
+ clean: (project) => clean3(state, project),
168727
169641
  buildReferences: (project, cancellationToken, writeFile2, getCustomTransformers) => build(
168728
169642
  state,
168729
169643
  project,
@@ -168733,7 +169647,7 @@ ${lanes.join("\n")}
168733
169647
  /*onlyReferences*/
168734
169648
  true
168735
169649
  ),
168736
- cleanReferences: (project) => clean2(
169650
+ cleanReferences: (project) => clean3(
168737
169651
  state,
168738
169652
  project,
168739
169653
  /*onlyReferences*/
@@ -234928,7 +235842,7 @@ var require_clients = __commonJS({
234928
235842
  const serverApi = ctx.routes.filter((route) => route.kind === "api");
234929
235843
  const mountBases = [...new Set(serverApi.map((route) => route.path.split("/").slice(0, 2).join("/")).filter((base) => base && base !== "/"))];
234930
235844
  for (const call of clientCalls) {
234931
- let path = (0, shared_1.stripTrailingSlashes)(call.path) || "/";
235845
+ const path = (0, shared_1.stripTrailingSlashes)(call.path) || "/";
234932
235846
  if (/\.(png|jpe?g|svg|gif|webp|css|js|json|ico|mp3|mp4|woff2?)$/.test(path))
234933
235847
  continue;
234934
235848
  const candidates = [path, ...mountBases.map((base) => (0, scan_js_1.joinPath)(base, path))];