@validate.qa/runner 1.0.100 → 1.0.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +3 -0
  2. package/dist/{chunk-MBKGTELT.mjs → chunk-3JM6XTSZ.mjs} +1862 -530
  3. package/dist/{chunk-2XD63NU5.mjs → chunk-4WLP3RWN.mjs} +941 -86
  4. package/dist/{chunk-MHFP3QXU.mjs → chunk-5DFBEL72.mjs} +1 -1
  5. package/dist/{chunk-II7QW7MZ.mjs → chunk-BXLEPT4M.mjs} +31 -18
  6. package/dist/{chunk-MAO6ADAO.mjs → chunk-CBVHPYVJ.mjs} +62 -2
  7. package/dist/{chunk-5P4ANVDY.mjs → chunk-DN3O6EYT.mjs} +43073 -23357
  8. package/dist/{chunk-GIIIVO2W.mjs → chunk-E2RRAE27.mjs} +155 -11
  9. package/dist/chunk-E2VEMBQQ.mjs +291 -0
  10. package/dist/{chunk-HMUC3DKS.mjs → chunk-HJ6FKHMM.mjs} +2281 -2
  11. package/dist/{chunk-WDXLAZ2J.mjs → chunk-PWFWMVZU.mjs} +3 -2
  12. package/dist/{chunk-6NIT2CCH.mjs → chunk-ZZYBRIBT.mjs} +18 -12
  13. package/dist/cli.js +139723 -114754
  14. package/dist/cli.mjs +536 -247
  15. package/dist/credential-mcp-server.js +1761 -358
  16. package/dist/credential-mcp-server.mjs +3 -3
  17. package/dist/discovery-mcp-server.js +1874 -379
  18. package/dist/discovery-mcp-server.mjs +4 -4
  19. package/dist/dist-52M5RDZ6.mjs +17 -0
  20. package/dist/inbox-mcp-server.js +1902 -505
  21. package/dist/inbox-mcp-server.mjs +3 -3
  22. package/dist/route-seed-worker.js +3976 -356
  23. package/dist/route-seed-worker.mjs +14 -7
  24. package/dist/run-write-guard-preload.js +2908 -572
  25. package/dist/run-write-guard-preload.mjs +5 -285
  26. package/dist/run-write-guard.js +2908 -572
  27. package/dist/run-write-guard.mjs +3 -3
  28. package/dist/spec-driver.js +1 -1
  29. package/dist/spec-driver.mjs +1 -1
  30. package/package.json +4 -3
  31. package/dist/dist-DFNLMH6L.mjs +0 -16
@@ -105,13 +105,13 @@ function isBlockedEnvVar(key) {
105
105
  function sanitizeTestVariables(vars) {
106
106
  if (!vars)
107
107
  return {};
108
- const clean2 = {};
108
+ const clean3 = {};
109
109
  for (const [key, value] of Object.entries(vars)) {
110
110
  if (!isBlockedEnvVar(key)) {
111
- clean2[key] = value;
111
+ clean3[key] = value;
112
112
  }
113
113
  }
114
- return clean2;
114
+ return clean3;
115
115
  }
116
116
  function buildSandboxedEnv(processEnv, testVariables) {
117
117
  const env = {};
@@ -1619,8 +1619,11 @@ var init_discovery_har_decision = __esm({
1619
1619
  });
1620
1620
 
1621
1621
  // ../shared/dist/run-classification.js
1622
- function isUserCancelledError(error) {
1623
- return typeof error === "string" && error.trim().startsWith(USER_CANCELLED_RUN_ERROR);
1622
+ function isCancelledReason(error) {
1623
+ if (typeof error !== "string")
1624
+ return false;
1625
+ const text = error.trim();
1626
+ return CANCELLED_REASON_PREFIXES.some((prefix) => text.startsWith(prefix));
1624
1627
  }
1625
1628
  function isHeldHealRun(run) {
1626
1629
  return run?.status === "PASSED" && typeof run.error === "string" && run.error.startsWith(HEAL_PATCH_HELD_RUN_ERROR_PREFIX);
@@ -1653,7 +1656,7 @@ function pickLatestHealthRun(runs) {
1653
1656
  for (const run of runs) {
1654
1657
  if (run.mode === "auth-setup")
1655
1658
  continue;
1656
- if (run.status === "ERROR" && isUserCancelledError(run.error))
1659
+ if (run.status === "ERROR" && isCancelledReason(run.error))
1657
1660
  continue;
1658
1661
  if (isHeldHealRun(run))
1659
1662
  continue;
@@ -1671,6 +1674,15 @@ function pickDisplayRun(runs) {
1671
1674
  return latest;
1672
1675
  return pickLatestHealthRun(runs) ?? latest;
1673
1676
  }
1677
+ function featureRowRunStatus(runs) {
1678
+ const latest = runs.find((run) => run.mode !== "auth-setup");
1679
+ if (!latest)
1680
+ return null;
1681
+ const decisive = pickLatestHealthRun(runs.filter((run) => !isEnvironmentalError(run)));
1682
+ if (decisive)
1683
+ return decisive.status;
1684
+ return latest.status === "PENDING" || latest.status === "RUNNING" ? latest.status : null;
1685
+ }
1674
1686
  function isEnvironmentalError(run) {
1675
1687
  if (!run)
1676
1688
  return false;
@@ -1702,7 +1714,7 @@ function classifyErrorMessage(error) {
1702
1714
  return "config_issue";
1703
1715
  if (EMAIL_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
1704
1716
  return "email_not_received";
1705
- if (isUserCancelledError(error))
1717
+ if (isCancelledReason(error))
1706
1718
  return "cancelled";
1707
1719
  if (isRetriedRunInterruption(error))
1708
1720
  return "interrupted";
@@ -1737,7 +1749,7 @@ function heldHealRunWhere() {
1737
1749
  function excludeCancelledRunsWhere() {
1738
1750
  return {
1739
1751
  NOT: [
1740
- { status: "ERROR", error: { not: null, startsWith: USER_CANCELLED_RUN_ERROR } },
1752
+ { status: "ERROR", OR: CANCELLED_REASON_PREFIXES.map((prefix) => ({ error: { not: null, startsWith: prefix } })) },
1741
1753
  heldHealRunWhere()
1742
1754
  ]
1743
1755
  };
@@ -1815,11 +1827,25 @@ function reportedBugWhere() {
1815
1827
  function bugReviewStatus(status) {
1816
1828
  return status === "QUARANTINED" ? "confirmed" : "needs-review";
1817
1829
  }
1818
- var USER_CANCELLED_RUN_ERROR, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
1830
+ 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;
1819
1831
  var init_run_classification = __esm({
1820
1832
  "../shared/dist/run-classification.js"() {
1821
1833
  "use strict";
1822
1834
  USER_CANCELLED_RUN_ERROR = "Cancelled by user";
1835
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR = "Cancelled by platform support";
1836
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR = "Cancelled by platform admin";
1837
+ ADMIN_FORCE_CANCELLED_RUN_ERROR = "Admin force-cancelled all active runs";
1838
+ CHAIN_CANCELLED_RUN_ERROR = "Cancelled: parent run cancelled";
1839
+ RETRY_HELD_RUN_ERROR = "Cancelled: auto generation paused";
1840
+ CANCELLED_REASON_PREFIXES = [
1841
+ USER_CANCELLED_RUN_ERROR,
1842
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR,
1843
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR,
1844
+ ADMIN_FORCE_CANCELLED_RUN_ERROR,
1845
+ CHAIN_CANCELLED_RUN_ERROR,
1846
+ RETRY_HELD_RUN_ERROR
1847
+ ];
1848
+ isUserCancelledError = isCancelledReason;
1823
1849
  HEAL_PATCH_HELD_RUN_ERROR_PREFIX = "Heal patch held";
1824
1850
  RUNNER_INTERRUPT_ERROR_PREFIX = "Runner shutting down";
1825
1851
  SERVER_INTERRUPT_ERROR_PREFIX = "Run interrupted by server restart";
@@ -1835,6 +1861,7 @@ var init_run_classification = __esm({
1835
1861
  UNEXPECTED_PASS_ERROR_PREFIX = "UNEXPECTED_PASS:";
1836
1862
  BUG_TEST_BROKEN_ERROR_PREFIX = "BUG_TEST_BROKEN:";
1837
1863
  BUG_TEST_BROKEN_REASON = "bug test broken (not a bug verdict)";
1864
+ TEST_DID_NOT_RUN_ERROR = "Test did not run (a dependency failed or the run was interrupted)";
1838
1865
  EMAIL_NOT_RECEIVED_PREFIXES = [
1839
1866
  "VERIFICATION_EMAIL_NOT_RECEIVED:",
1840
1867
  "VERIFICATION_EMAIL_HAS_NO_CODE:",
@@ -1847,9 +1874,10 @@ var init_run_classification = __esm({
1847
1874
  ];
1848
1875
  ENVIRONMENTAL_ERROR_STORED_PREFIXES = [
1849
1876
  ...ENVIRONMENTAL_ERROR_PREFIXES.flatMap((prefix) => [prefix, `Error: ${prefix}`]),
1850
- // Written by the cancel routes as is, with no prefix wrapper.
1851
- USER_CANCELLED_RUN_ERROR
1877
+ // Written by the cancels as is, with no prefix wrapper.
1878
+ ...CANCELLED_REASON_PREFIXES
1852
1879
  ];
1880
+ FEATURE_ROW_RUN_WINDOW = 10;
1853
1881
  BUG_SURFACE_SUPPRESSING_TAGS = ["@rate-limit-blocked", "@quota-exceeded", "@auth-lost"];
1854
1882
  QUARANTINE_CLOSE_TAGS = [
1855
1883
  "@bug-found",
@@ -2938,176 +2966,6 @@ var init_origin_domain = __esm({
2938
2966
  }
2939
2967
  });
2940
2968
 
2941
- // ../shared/dist/auth-block-detector.js
2942
- function stripEchoLines(text) {
2943
- return text.split("\n").filter((line) => !ECHO_LINE_RE.test(line)).join("\n");
2944
- }
2945
- function matchesAny(text, patterns) {
2946
- return patterns.some((p) => p.test(text));
2947
- }
2948
- function detectAuthBlock(input) {
2949
- const tags = input.tags ?? [];
2950
- const isAuthed = tags.some((t) => AUTHED_TAGS.includes(t));
2951
- if (!isAuthed)
2952
- return NO_DETECTION;
2953
- const meta = [input.testName, input.description].filter(Boolean).join(" ");
2954
- if (matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH))
2955
- return NO_DETECTION;
2956
- const sources = [];
2957
- if (input.error)
2958
- sources.push(stripEchoLines(input.error));
2959
- if (input.logs)
2960
- sources.push(stripEchoLines(input.logs));
2961
- for (const sr of input.stepResults ?? []) {
2962
- if (sr.error)
2963
- sources.push(stripEchoLines(sr.error));
2964
- }
2965
- for (const text of sources) {
2966
- if (!text)
2967
- continue;
2968
- for (const sig of AUTH_BLOCK_SIGNATURES) {
2969
- if (sig.re.test(text)) {
2970
- return {
2971
- detected: true,
2972
- reason: sig.reason,
2973
- evidence: [`login-block signature "${sig.reason}" in: "${text.slice(0, 140).replace(/\s+/g, " ").trim()}"`]
2974
- };
2975
- }
2976
- }
2977
- }
2978
- return NO_DETECTION;
2979
- }
2980
- function detectApiColdSessionBlock(input) {
2981
- const tags = input.tags ?? [];
2982
- if (!tags.includes("@api"))
2983
- return NO_DETECTION;
2984
- const cookieMissing = input.authStateExpected && !input.authStateInjected;
2985
- const standingMissing = !!input.needsStandingHeaders && !input.standingHeadersInjected;
2986
- if (!cookieMissing && !standingMissing)
2987
- return NO_DETECTION;
2988
- const has401Status = (input.apiCalls ?? []).some((c) => c && (c.status === 401 || c.status === 403));
2989
- const texts = [];
2990
- if (input.error)
2991
- texts.push(input.error);
2992
- if (input.logs)
2993
- texts.push(input.logs);
2994
- for (const sr of input.stepResults ?? [])
2995
- if (sr.error)
2996
- texts.push(sr.error);
2997
- const has401Text = texts.some((t) => UNAUTHORIZED_TEXT_RE.test(t));
2998
- if (!has401Status && !has401Text)
2999
- return NO_DETECTION;
3000
- const missingAuth = cookieMissing ? "cold injected auth session (401/403 with no storageState)" : "missing standing auth header (401/403 with no API_AUTH_HEADERS installed)";
3001
- const injectedNoun = cookieMissing ? "injected session was absent/expired" : "standing auth header was not installed";
3002
- return {
3003
- detected: true,
3004
- reason: missingAuth,
3005
- evidence: [
3006
- has401Status ? `a captured @api call returned 401/403 while the ${injectedNoun}` : `a 401/403 signal in the run error/logs while the ${injectedNoun}`
3007
- ]
3008
- };
3009
- }
3010
- function coldSessionConfigMessage(context, reason, evidence) {
3011
- return `CONFIG_ISSUE: ${context} without valid injected auth (${reason}) \u2014 no session cookie was injected this run. Ensure the project login (auth-setup recipe / prewarm) is configured so the shared session jar warms (a project with no login: set TEST_USER_EMAIL and TEST_USER_PASSWORD in Test Data \u2192 Test Variables so a session can be captured); for endpoints that authenticate with a standing API key or bearer header instead, set them in the project's API_AUTH_HEADERS variable (injected as request headers). Evidence: ${evidence.join("; ")}`;
3012
- }
3013
- var NO_DETECTION, AUTHED_TAGS, LOGIN_CTX, AUTH_BLOCK_SIGNATURES, INTENTIONAL_NEGATIVE_AUTH, ECHO_LINE_RE, UNAUTHORIZED_TEXT_RE;
3014
- var init_auth_block_detector = __esm({
3015
- "../shared/dist/auth-block-detector.js"() {
3016
- "use strict";
3017
- NO_DETECTION = { detected: false, reason: "", evidence: [] };
3018
- AUTHED_TAGS = ["@needs-auth", "@inline-login"];
3019
- LOGIN_CTX = "\\b(?:log\\s*in|login|logging\\s+in|sign[\\s-]?in|signing\\s+in|continue)\\b";
3020
- AUTH_BLOCK_SIGNATURES = [
3021
- { reason: "incorrect password", re: /\b(incorrect|invalid|wrong|bad)\s+password\b/i },
3022
- { reason: "password is incorrect", re: /\bpassword\s+(is\s+)?(incorrect|invalid|wrong)\b/i },
3023
- { reason: "invalid credentials", re: /\b(invalid|incorrect|wrong|bad)\s+(login\s+)?credentials\b/i },
3024
- { reason: "credentials do not match", re: /\bcredentials\b[^\n]{0,40}\b(do(?:es)?\s*n['’]?t|do\s+not)\s+match\b/i },
3025
- { reason: "incorrect email or password", re: /\b(incorrect|invalid|wrong)\s+(email|username)\s+or\s+password\b/i },
3026
- // Require an EXPLICIT negative (codex #21): the old `(not\s+)?(confirmed|verified)` made the negation
3027
- // optional, so a benign "Email verified" success message / feature assertion matched and hid a real
3028
- // feature failure as a CONFIG_ISSUE. Only "email not (yet) confirmed/verified" or "unconfirmed/unverified".
3029
- { reason: "email not confirmed", re: /\b(e-?mail)\b[^\n]{0,30}\b(not\s+(?:yet\s+)?(?:confirmed|verified)|unconfirmed|unverified)\b/i },
3030
- { reason: "please confirm your email", re: /\bplease\s+(confirm|verify)\s+your\s+(e-?mail)\b/i },
3031
- // The bare "confirm/verify your email" and "account not found" phrases are ALSO ordinary feature copy
3032
- // ("Verify your email banner", a 404-style "Account not found" page), so they only count with a
3033
- // login/sign-in context on the SAME line (either side). "Please confirm/verify your email" above
3034
- // is already an explicit prompt and stays unconditional.
3035
- { reason: "confirm your email to continue", re: new RegExp(`${LOGIN_CTX}[^\\n]{0,80}\\bconfirm\\s+your\\s+e-?mail\\b|\\bconfirm\\s+your\\s+e-?mail\\b[^\\n]{0,80}${LOGIN_CTX}`, "i") },
3036
- { reason: "verify your email to continue", re: new RegExp(`${LOGIN_CTX}[^\\n]{0,80}\\bverify\\s+your\\s+e-?mail\\b|\\bverify\\s+your\\s+e-?mail\\b[^\\n]{0,80}${LOGIN_CTX}`, "i") },
3037
- {
3038
- reason: "account not found",
3039
- re: new RegExp(`${LOGIN_CTX}[^\\n]{0,80}\\baccount\\b[^\\n]{0,20}\\b(?:not\\s+found|does(?:n['\u2019]t| not)\\s+exist)\\b|\\baccount\\b[^\\n]{0,20}\\b(?:not\\s+found|does(?:n['\u2019]t| not)\\s+exist)\\b[^\\n]{0,80}${LOGIN_CTX}`, "i")
3040
- },
3041
- { reason: "authentication failed", re: /\bauthentication\s+failed\b/i },
3042
- { reason: "login failed", re: /\b(login|log\s*in|sign[\s-]?in)\s+failed\b/i }
3043
- ];
3044
- INTENTIONAL_NEGATIVE_AUTH = [
3045
- /\b(invalid|incorrect|wrong|bad)\s+(password|credentials|email|login)\b/i,
3046
- /\b(negative|unhappy)\s+(login|auth|sign[\s-]?in|path|case|flow|test)\b/i,
3047
- // "should fail" et al must be TIED to an auth noun (codex #21): a bare `should\s+(fail|reject|deny)`
3048
- // exempted benign names like "Export should fail gracefully", silently disabling the anti-lockout
3049
- // protection for them. Require a login/auth noun on either side of the "should fail / fail to log in".
3050
- /\b(login|log\s*in|sign[\s-]?in|auth\w*|credential|password)\b[^\n]{0,40}\bshould\s+(fail|reject|deny)\b/i,
3051
- /\bshould\s+(fail|reject|deny|not\s+log(?:\s*in)?)\b[^\n]{0,40}\b(login|log\s*in|sign[\s-]?in|auth\w*|credential|password|unauthenticated)\b/i,
3052
- /\bfailed?\s+login\b/i,
3053
- /\blogin\s+(error|failure|rejected?)\b/i,
3054
- /\b(reject|deny|denied|block(ed)?)\b[^\n]{0,20}\b(login|sign[\s-]?in|auth|credential|password)\b/i
3055
- ];
3056
- ECHO_LINE_RE = /^\s*(?:(?:📋|🎭|🌐|⚙️?)|(?:stderr:\s*|stdout:\s*)?Locator:)/u;
3057
- 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;
3058
- }
3059
- });
3060
-
3061
- // ../shared/dist/skip-coverage-detector.js
3062
- function detectSkippedApiCoverage(steps) {
3063
- const allSkipped = steps.length > 0 && steps.every((s) => s.status === "skipped");
3064
- if (allSkipped) {
3065
- if (steps.every((s) => s.skipKind === "fixme")) {
3066
- return {
3067
- detected: false,
3068
- logLine: "All @api tests skipped, but EVERY skip is a deliberate fixme (intentional N/A \u2014 e.g. a UI-only redirect login) \u2014 NOT relabelling CONFIG_ISSUE"
3069
- };
3070
- }
3071
- return {
3072
- detected: true,
3073
- error: (
3074
- // Name BOTH auth mechanisms (item 5d): the all-skipped copy used to say only "refresh the auth
3075
- // state", which misdirects a @auth-standing operator (whose real gap is a missing API_AUTH_HEADERS
3076
- // key, not a cold cookie session) to warm a login that was never the problem.
3077
- "CONFIG_ISSUE: Authenticated @api coverage was NOT exercised \u2014 every test skipped (cold injected session, a missing standing auth header, or an unresolved fixme). Provide the auth the endpoints need \u2014 warm the project login/session cookie (auth-setup recipe / prewarm), or set a standing API key/bearer in the project's API_AUTH_HEADERS variable \u2014 so the assertions actually run."
3078
- ),
3079
- logLine: "All @api tests skipped \u2014 posting ERROR/CONFIG_ISSUE (not PASSED) so zero authenticated coverage is visible, not silent green"
3080
- };
3081
- }
3082
- const runtimeSkipped = steps.filter((s) => s.status === "skipped" && s.skipKind === "skip");
3083
- if (runtimeSkipped.length === 0)
3084
- return { detected: false };
3085
- const reasons = [...new Set(runtimeSkipped.map((s) => `"${s.title ?? "test"}"${s.skipReason ? ` (${s.skipReason})` : ""}`))].join("; ");
3086
- return {
3087
- detected: true,
3088
- error: `CONFIG_ISSUE: ${runtimeSkipped.length} of ${steps.length} @api tests skipped at runtime \u2014 that coverage was NOT exercised. Skipped: ${reasons}`,
3089
- logLine: `${runtimeSkipped.length}/${steps.length} @api tests skipped at runtime \u2014 posting ERROR/CONFIG_ISSUE (not PASSED) so the coverage hole is visible, not silent green`
3090
- };
3091
- }
3092
- var init_skip_coverage_detector = __esm({
3093
- "../shared/dist/skip-coverage-detector.js"() {
3094
- "use strict";
3095
- }
3096
- });
3097
-
3098
- // ../shared/dist/feature-flags.js
3099
- function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
3100
- return value?.trim().toLowerCase() !== "false";
3101
- }
3102
- function isAuthReadinessGuardEnabled(value = process.env.AUTH_READINESS_GUARD) {
3103
- return value?.trim().toLowerCase() !== "false";
3104
- }
3105
- var init_feature_flags = __esm({
3106
- "../shared/dist/feature-flags.js"() {
3107
- "use strict";
3108
- }
3109
- });
3110
-
3111
2969
  // ../shared/dist/repository-cli.js
3112
2970
  function isRepositoryCli(value) {
3113
2971
  return typeof value === "string" && REPOSITORY_CLIS.includes(value);
@@ -3303,9 +3161,40 @@ function semanticTarget(label, platform, base) {
3303
3161
  }
3304
3162
  };
3305
3163
  }
3164
+ function androidResourceOwners(value) {
3165
+ const owners = [];
3166
+ for (const match of value.matchAll(ANDROID_RESOURCE_ID_SUFFIX_RE)) {
3167
+ let runStart = match.index;
3168
+ while (runStart > 0 && /[a-z0-9_.]/i.test(value[runStart - 1]))
3169
+ runStart--;
3170
+ const owner = ownerAtEndOfRun(value.slice(runStart, match.index));
3171
+ if (owner !== null)
3172
+ owners.push(owner);
3173
+ }
3174
+ return owners;
3175
+ }
3176
+ function ownerAtEndOfRun(run) {
3177
+ const segments = run.split(".");
3178
+ const last = segments.length - 1;
3179
+ const startsWithLetter = (segment) => /^[a-z]/i.test(segment);
3180
+ if (last < 1 || !startsWithLetter(segments[last]))
3181
+ return null;
3182
+ let first = last;
3183
+ while (first > 0 && startsWithLetter(segments[first - 1]))
3184
+ first--;
3185
+ let offset = 0;
3186
+ for (let i = 0; i < first - 1; i++)
3187
+ offset += segments[i].length + 1;
3188
+ if (first > 0) {
3189
+ const letterAt = segments[first - 1].search(/[a-z]/i);
3190
+ if (letterAt >= 0)
3191
+ return run.slice(offset + letterAt);
3192
+ offset += segments[first - 1].length + 1;
3193
+ }
3194
+ return first < last ? run.slice(offset) : null;
3195
+ }
3306
3196
  function containsForeignAppPackage(value, targetAppId) {
3307
- for (const match of value.matchAll(ANDROID_RESOURCE_OWNER_RE)) {
3308
- const owner = match[1];
3197
+ for (const owner of androidResourceOwners(value)) {
3309
3198
  if (targetAppId && owner !== targetAppId && !owner.startsWith(`${targetAppId}.`))
3310
3199
  return true;
3311
3200
  }
@@ -3442,7 +3331,7 @@ function healMobileTestSteps(input) {
3442
3331
  summary: changed ? uniq(summary).slice(0, 12) : []
3443
3332
  };
3444
3333
  }
3445
- var MOBILE_STEP_ACTIONS, FOREIGN_PACKAGE_RE, ANDROID_RESOURCE_OWNER_RE, COMMON_EXTERNAL_PREFIXES;
3334
+ var MOBILE_STEP_ACTIONS, FOREIGN_PACKAGE_RE, ANDROID_RESOURCE_ID_SUFFIX_RE, COMMON_EXTERNAL_PREFIXES;
3446
3335
  var init_mobile_step_healer = __esm({
3447
3336
  "../shared/dist/mobile-step-healer.js"() {
3448
3337
  "use strict";
@@ -3463,7 +3352,7 @@ var init_mobile_step_healer = __esm({
3463
3352
  "assertStateDelta"
3464
3353
  ]);
3465
3354
  FOREIGN_PACKAGE_RE = /\b[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*){2,}\b/gi;
3466
- ANDROID_RESOURCE_OWNER_RE = /([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+):id\//gi;
3355
+ ANDROID_RESOURCE_ID_SUFFIX_RE = /:id\//gi;
3467
3356
  COMMON_EXTERNAL_PREFIXES = [
3468
3357
  "com.android.",
3469
3358
  "com.google.",
@@ -4499,7 +4388,7 @@ function unescapeQuotedBody(raw) {
4499
4388
  return raw.replace(/\\(['"`\\])/g, "$1");
4500
4389
  }
4501
4390
  function extractRoleNameValue(line) {
4502
- const m = line.match(new RegExp(`getByRole\\s*\\([^,]+,\\s*\\{[^}]*name\\s*:\\s*(?:${QUOTED_BODY}|\\/([^/]*?)\\/)`, "i"));
4391
+ const m = line.match(new RegExp(`getByRole\\s*\\([^,]{1,200},\\s*\\{[^}]{0,200}?name\\s*:\\s*(?:${QUOTED_BODY}|\\/([^/]*?)\\/)`, "i"));
4503
4392
  if (!m)
4504
4393
  return null;
4505
4394
  const raw = m[1] ?? m[2] ?? m[3];
@@ -4896,7 +4785,7 @@ var init_assertion_classifier = __esm({
4896
4785
  // toHaveCount(0) stays tier 4 (absence proof) via TIER4_PATTERNS.
4897
4786
  [/\.toHaveCount\s*\(\s*[1-9]\d*\s*\)/, "non-zero toHaveCount asserts a record count that changes between runs"],
4898
4787
  // parenthesised counts like "(3)" or "[12]" in text - but not Playwright method args
4899
- [/(?<!\.\w{3,20}\s*)\(\s*\d+\s*\)(?!\s*[,;]?\s*\))/i, "parenthesised count is session-specific"],
4788
+ [/\((?<!\.\w{3,20}\s*\()\s*\d+\s*\)(?!\s*(?:[,;]\s*)?\))/i, "parenthesised count is session-specific"],
4900
4789
  // bare email addresses in assertion content
4901
4790
  [/\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b/i, "email address is user-specific"],
4902
4791
  // currency/balance amounts (dynamic account data)
@@ -5142,7 +5031,7 @@ var init_assertion_proof = __esm({
5142
5031
  PRESENCE_MATCHER = /\.(?:toBeVisible|toBeAttached|toBeEnabled|toBeInViewport|toBeEditable|toBeFocused)\s*\(|\.toHaveCount\s*\(\s*1\s*\)/;
5143
5032
  TEXT_MATCHER = /\.(?:toHaveText|toContainText)\s*\(/;
5144
5033
  ANY_VALUE_SOURCE = /^(?:\.|\\S|\\w|\\d|\[\^\\s\]|\[0-9\]|\[\\d\]|\[\\w\]|\[\\s\\S\])[+*]?\??$/;
5145
- ANY_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(\s*\/((?:\\.|[^/\\]){1,300})\/[a-z]*\s*\)/;
5034
+ ANY_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(\s*\/((?:\\.|[^/\\]){1,300})\/[a-z]*\s*[,)]/;
5146
5035
  EMPTY_NOT_VALUE = /\.not\s*\.\s*toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/;
5147
5036
  TEXT_MATCHER_LITERAL = /\.(?:toHaveText|toContainText)\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/;
5148
5037
  GET_BY_TEXT_LITERAL = /getByText\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/;
@@ -5159,7 +5048,7 @@ var init_assertion_proof = __esm({
5159
5048
  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;
5160
5049
  TEXT_LOCATOR = /\.getByText\s*\(|\.filter\s*\(\s*\{\s*hasText\s*:/g;
5161
5050
  CSS_LOCATOR = /\.locator\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/g;
5162
- SPECIFIC_CSS = /\[[^\]]*=|#[\w-]|has-text|:text/;
5051
+ SPECIFIC_CSS = /\[[^\][=]*=|#[\w-]|has-text|:text/;
5163
5052
  }
5164
5053
  });
5165
5054
 
@@ -5207,6 +5096,8 @@ function stripCoverageComments(code) {
5207
5096
  let copyFrom = 0;
5208
5097
  let i = 0;
5209
5098
  let quote = null;
5099
+ let prev = "";
5100
+ let noRegexBefore = 0;
5210
5101
  while (i < code.length) {
5211
5102
  const ch = code[i];
5212
5103
  if (quote) {
@@ -5214,12 +5105,24 @@ function stripCoverageComments(code) {
5214
5105
  i += 2;
5215
5106
  continue;
5216
5107
  }
5217
- if (ch === quote || ch === "\n" && quote !== "`")
5108
+ if (ch === quote || ch === "\n" && quote !== "`") {
5218
5109
  quote = null;
5110
+ prev = ch === "\n" ? prev : ch;
5111
+ }
5219
5112
  i++;
5220
5113
  continue;
5221
5114
  }
5222
5115
  const next = code[i + 1];
5116
+ if (ch === "/" && next !== "*" && next !== "/" && i >= noRegexBefore && (prev === "" || REGEX_PRECEDER2.includes(prev))) {
5117
+ const end = regexLiteralEnd3(code, i);
5118
+ if (end > 0) {
5119
+ i = end;
5120
+ prev = "a";
5121
+ continue;
5122
+ }
5123
+ const eol = code.indexOf("\n", i);
5124
+ noRegexBefore = eol === -1 ? code.length : eol;
5125
+ }
5223
5126
  if (ch === "/" && next === "*") {
5224
5127
  const end = code.indexOf("*/", i + 2);
5225
5128
  const stop = end === -1 ? code.length : end + 2;
@@ -5238,11 +5141,39 @@ function stripCoverageComments(code) {
5238
5141
  }
5239
5142
  if (ch === '"' || ch === "'" || ch === "`")
5240
5143
  quote = ch;
5144
+ if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r")
5145
+ prev = ch;
5241
5146
  i++;
5242
5147
  }
5243
5148
  out.push(code.slice(copyFrom));
5244
5149
  return out.join("");
5245
5150
  }
5151
+ function regexLiteralEnd3(code, start) {
5152
+ let inClass = false;
5153
+ for (let j = start + 1; j < code.length; j++) {
5154
+ const c = code[j];
5155
+ if (c === "\n")
5156
+ return -1;
5157
+ if (c === "\\") {
5158
+ j++;
5159
+ continue;
5160
+ }
5161
+ if (inClass) {
5162
+ if (c === "]")
5163
+ inClass = false;
5164
+ continue;
5165
+ }
5166
+ if (c === "[")
5167
+ inClass = true;
5168
+ else if (c === "/") {
5169
+ let k = j + 1;
5170
+ while (k < code.length && isIdentChar(code[k]))
5171
+ k++;
5172
+ return k;
5173
+ }
5174
+ }
5175
+ return -1;
5176
+ }
5246
5177
  function isIdentChar(ch) {
5247
5178
  if (!ch)
5248
5179
  return false;
@@ -5900,7 +5831,7 @@ function assessTitleCoverage(title, code) {
5900
5831
  const repeatedHit = [...repeated].some((term) => hay.includes(term));
5901
5832
  return { covered: hit >= needed || repeatedHit, missingTerms };
5902
5833
  }
5903
- var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
5834
+ var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, REGEX_PRECEDER2, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
5904
5835
  var init_expected_outcome_coverage = __esm({
5905
5836
  "../shared/dist/expected-outcome-coverage.js"() {
5906
5837
  "use strict";
@@ -5937,6 +5868,7 @@ var init_expected_outcome_coverage = __esm({
5937
5868
  ]);
5938
5869
  MAX_ASSERTION_JOIN_CHARS = 8e3;
5939
5870
  MAX_COVERAGE_LINE_CHARS = 2e3;
5871
+ REGEX_PRECEDER2 = "(,=:[!&|?{};+-*%<>~^";
5940
5872
  BLOCK_SCAFFOLD = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
5941
5873
  cachedAssertionInput = null;
5942
5874
  cachedAssertionSource = { raw: "", normalized: "", bindings: /* @__PURE__ */ new Map(), bindingRefs: /* @__PURE__ */ new Map() };
@@ -6080,22 +6012,37 @@ function classifyAuthHandoffIntent(text) {
6080
6012
  function withoutIdpUrlAsserts(code) {
6081
6013
  return code.replace(/\.(?:toHaveURL|waitForURL)\s*\((?:[^)(]|\([^)]*\))*\)/g, (match) => /accountchooser|accounts\.google|appleid\.apple|login\.microsoftonline|\/v\d+\/signin|oauth/i.test(match) ? "" : match);
6082
6014
  }
6015
+ function stripLineComment(line) {
6016
+ let from = line.length;
6017
+ while (from > 0) {
6018
+ const c = line.charCodeAt(from - 1);
6019
+ if (c === 10 || c === 13 || c === 8232 || c === 8233)
6020
+ break;
6021
+ from--;
6022
+ }
6023
+ const at = line.indexOf("//", from);
6024
+ return at < 0 ? line : line.slice(0, at);
6025
+ }
6083
6026
  function stripComments(code) {
6084
- return replaceBlockComments(code, () => "").split("\n").map((line) => line.replace(/\/\/.*$/, "")).join("\n");
6027
+ return replaceBlockComments(code, () => "").split("\n").map(stripLineComment).join("\n");
6085
6028
  }
6086
- function balancedCall(code, start) {
6087
- const open = code.indexOf("(", start);
6088
- if (open < 0)
6089
- return null;
6090
- let depth = 0;
6091
- for (let i = open; i < code.length; i++) {
6029
+ function closingParens(code) {
6030
+ const close = new Int32Array(code.length).fill(-1);
6031
+ const open = [];
6032
+ for (let i = 0; i < code.length; i++) {
6092
6033
  const c = code[i];
6093
6034
  if (c === "(")
6094
- depth++;
6095
- else if (c === ")" && --depth === 0)
6096
- return { inner: code.slice(open + 1, i), end: i };
6035
+ open.push(i);
6036
+ else if (c === ")" && open.length > 0)
6037
+ close[open.pop()] = i;
6097
6038
  }
6098
- return null;
6039
+ return close;
6040
+ }
6041
+ function balancedCall(code, close, start) {
6042
+ const open = code.indexOf("(", start);
6043
+ if (open < 0 || close[open] < 0)
6044
+ return null;
6045
+ return { inner: code.slice(open + 1, close[open]), end: close[open] };
6099
6046
  }
6100
6047
  function resolveLocator(code, subject) {
6101
6048
  const id = subject.trim().match(/^([A-Za-z_$][\w$]*)$/);
@@ -6143,8 +6090,10 @@ function isAuthHandoffDismissCode(code) {
6143
6090
  const expects = [];
6144
6091
  const re = new RegExp(EXPECT_CALL_RE.source, "g");
6145
6092
  let match;
6093
+ let close = null;
6146
6094
  while (match = re.exec(body)) {
6147
- const found = balancedCall(body, match.index);
6095
+ close ??= closingParens(body);
6096
+ const found = balancedCall(body, close, match.index);
6148
6097
  if (found)
6149
6098
  expects.push({ inner: found.inner, tail: body.slice(found.end, found.end + 48) });
6150
6099
  }
@@ -6189,35 +6138,232 @@ var init_auth_handoff_coverage = __esm({
6189
6138
  }
6190
6139
  });
6191
6140
 
6192
- // ../shared/dist/assertion-strength.js
6193
- function liveExpectStatements(uncommented) {
6194
- const masked = maskedOutsideStrings(uncommented).split("\n");
6195
- return accumulateStatements(uncommented.split("\n")).filter((s) => EXPECT_STMT.test(s.stmt) && EXPECT_STMT.test(masked[s.startLine - 1] ?? ""));
6141
+ // ../shared/dist/auth-plan.js
6142
+ function hasInlineLoginSteps(playwrightCode) {
6143
+ if (!playwrightCode)
6144
+ return false;
6145
+ return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
6196
6146
  }
6197
- function stripLineComment(ln) {
6198
- let quote = null;
6199
- for (let i = 0; i < ln.length; i++) {
6200
- const c = ln[i];
6201
- if (quote) {
6202
- if (c === quote && ln[i - 1] !== "\\")
6203
- quote = null;
6204
- continue;
6205
- }
6206
- if (c === '"' || c === "'" || c === "`") {
6207
- quote = c;
6208
- continue;
6209
- }
6210
- if (c === "/" && ln[i + 1] === "/")
6211
- return ln.slice(0, i);
6212
- }
6213
- return ln;
6147
+ function hasGeneratedAuthGuard(playwrightCode) {
6148
+ if (!playwrightCode)
6149
+ return false;
6150
+ return /\/\/ >>> VALIDATE_QA_AUTH v(\d+)\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\1\b/.test(playwrightCode);
6214
6151
  }
6215
- function splitInteractionStatementsWithLines(lines) {
6216
- const out = [];
6217
- let depth = 0;
6218
- let scaffold = false;
6219
- for (let i = 0; i < lines.length; i++) {
6220
- const ln = stripLineComment(lines[i]).trim();
6152
+ function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
6153
+ if (!sharedAuthInjectEnabled)
6154
+ return false;
6155
+ if (tags.includes("@auth"))
6156
+ return false;
6157
+ if (tags.includes("@api"))
6158
+ return true;
6159
+ if (!tags.includes("@needs-auth"))
6160
+ return false;
6161
+ if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
6162
+ return true;
6163
+ const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
6164
+ const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
6165
+ return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
6166
+ }
6167
+ function codeReadsStoredCredentials(code) {
6168
+ if (!code)
6169
+ return false;
6170
+ if (/__vqaLogin\b/.test(code))
6171
+ return true;
6172
+ for (const m of code.matchAll(/TEST_[A-Z0-9_]+/g)) {
6173
+ if (/_(?:EMAIL|PASSWORD)/.test(m[0]))
6174
+ return true;
6175
+ }
6176
+ for (const m of code.matchAll(/process\.env\s*\[([^\]]*)\]?/g)) {
6177
+ if (!/^\s*(['"])[^'"`]*\1\s*$/.test(m[1]))
6178
+ return true;
6179
+ }
6180
+ return false;
6181
+ }
6182
+ function explicitAuthTypeTags(t) {
6183
+ const found = /* @__PURE__ */ new Set();
6184
+ for (const tag of t) {
6185
+ if (AUTH_TYPE_TAGS.includes(tag))
6186
+ found.add(tag);
6187
+ else if (Object.hasOwn(AUTH_TAG_ALIASES, tag))
6188
+ found.add(AUTH_TAG_ALIASES[tag]);
6189
+ }
6190
+ return [...found];
6191
+ }
6192
+ function invalidPlan(configurationError) {
6193
+ return {
6194
+ version: 1,
6195
+ source: "invalid",
6196
+ acquisition: "unsupported",
6197
+ presentation: "none",
6198
+ needsCookieState: false,
6199
+ needsStandingHeaders: false,
6200
+ standingHeadersMode: "none",
6201
+ needsLoginCreds: false,
6202
+ requiredEnvKeys: [],
6203
+ configurationError
6204
+ };
6205
+ }
6206
+ function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
6207
+ return {
6208
+ version: 1,
6209
+ source: "explicit",
6210
+ authTypeTag: authTypeTag2,
6211
+ acquisition,
6212
+ presentation,
6213
+ needsCookieState: opts.cookie,
6214
+ needsStandingHeaders: opts.standingMode === "required",
6215
+ standingHeadersMode: opts.standingMode,
6216
+ needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
6217
+ requiredEnvKeys: opts.requiredEnvKeys
6218
+ };
6219
+ }
6220
+ function deriveAuthPlan(tags, code, opts) {
6221
+ const t = Array.isArray(tags) ? tags : [];
6222
+ const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
6223
+ const isApi = t.includes("@api");
6224
+ const explicit = isApi ? explicitAuthTypeTags(t) : [];
6225
+ if (explicit.length > 0) {
6226
+ if (t.includes("@auth")) {
6227
+ return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
6228
+ }
6229
+ const loginCompose = explicit.filter((e) => e === AUTH_TAG_COOKIE_STATE || e === AUTH_TAG_LOGIN_COOKIE || e === AUTH_TAG_LOGIN_BEARER);
6230
+ if (explicit.length === 2 && explicit.includes(AUTH_TAG_STANDING) && loginCompose.length === 1) {
6231
+ const composedKeys = [...TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY];
6232
+ switch (loginCompose[0]) {
6233
+ case AUTH_TAG_COOKIE_STATE:
6234
+ return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "required", requiredEnvKeys: composedKeys });
6235
+ case AUTH_TAG_LOGIN_COOKIE:
6236
+ return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
6237
+ case AUTH_TAG_LOGIN_BEARER:
6238
+ return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
6239
+ }
6240
+ }
6241
+ if (explicit.length > 1) {
6242
+ return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
6243
+ }
6244
+ switch (explicit[0]) {
6245
+ case AUTH_TAG_UNSUPPORTED:
6246
+ return {
6247
+ ...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
6248
+ };
6249
+ case AUTH_TAG_NONE:
6250
+ return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
6251
+ case AUTH_TAG_STANDING:
6252
+ return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
6253
+ case AUTH_TAG_LOGIN_BEARER:
6254
+ return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
6255
+ case AUTH_TAG_LOGIN_COOKIE:
6256
+ return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
6257
+ case AUTH_TAG_COOKIE_STATE:
6258
+ return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
6259
+ }
6260
+ }
6261
+ const cookie = legacyNeedsCookieState(t, code, sharedFlag);
6262
+ const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
6263
+ const selfLoginWithoutStoredCreds = t.includes("@auth") && !t.includes("@needs-auth") && !isApi && !!code?.trim() && !/TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin\b/.test(code);
6264
+ const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
6265
+ const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
6266
+ return {
6267
+ version: 1,
6268
+ source: "legacy",
6269
+ acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
6270
+ presentation: authedLegacy ? "cookie" : "none",
6271
+ needsCookieState: cookie,
6272
+ needsStandingHeaders: false,
6273
+ // A legacy bare-@api test keeps injecting standing headers IF the project configured them
6274
+ // (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
6275
+ standingHeadersMode: isApi ? "legacy-if-configured" : "none",
6276
+ needsLoginCreds,
6277
+ requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
6278
+ };
6279
+ }
6280
+ function authTypeTag(tags) {
6281
+ const t = Array.isArray(tags) ? tags : [];
6282
+ const explicit = explicitAuthTypeTags(t);
6283
+ return explicit.length === 1 ? explicit[0] : void 0;
6284
+ }
6285
+ 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;
6286
+ var init_auth_plan = __esm({
6287
+ "../shared/dist/auth-plan.js"() {
6288
+ "use strict";
6289
+ AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
6290
+ AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
6291
+ AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
6292
+ AUTH_TAG_STANDING = "@auth-standing";
6293
+ AUTH_TAG_NONE = "@auth-none";
6294
+ AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
6295
+ AUTH_TYPE_TAGS = [
6296
+ AUTH_TAG_COOKIE_STATE,
6297
+ AUTH_TAG_LOGIN_COOKIE,
6298
+ AUTH_TAG_LOGIN_BEARER,
6299
+ AUTH_TAG_STANDING,
6300
+ AUTH_TAG_NONE,
6301
+ AUTH_TAG_UNSUPPORTED
6302
+ ];
6303
+ AUTH_TAG_ALIASES = {
6304
+ "@auth-cookie": AUTH_TAG_COOKIE_STATE,
6305
+ "@auth-bearer": AUTH_TAG_LOGIN_BEARER,
6306
+ "@auth-token": AUTH_TAG_LOGIN_BEARER,
6307
+ "@auth-api-key": AUTH_TAG_STANDING,
6308
+ "@auth-standing-header": AUTH_TAG_STANDING
6309
+ };
6310
+ TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
6311
+ API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
6312
+ }
6313
+ });
6314
+
6315
+ // ../shared/dist/assertion-strength.js
6316
+ function parseLastActionProofMode(raw) {
6317
+ const v = (raw ?? "").trim().toLowerCase();
6318
+ return v === "on" || v === "shadow" ? v : "off";
6319
+ }
6320
+ function isUrlProof(stmt) {
6321
+ if (URL_PROOF.test(stmt))
6322
+ return true;
6323
+ let calls = 0;
6324
+ for (const open of stmt.matchAll(POLL_OPEN)) {
6325
+ if (++calls > POLL_CALLS_MAX)
6326
+ break;
6327
+ const from = open.index + open[0].length;
6328
+ const args = balancedArguments(stmt, from, MATCHER_ARGS_MAX);
6329
+ if (args === null)
6330
+ continue;
6331
+ const subject = pollSubject(args);
6332
+ if (!POLL_PAGE_URL.test(subject))
6333
+ continue;
6334
+ if (!POLL_PAGE_URL_ONLY.test(subject) || PAGE_VALUE_MATCHER.test(stmt.slice(from + args.length)))
6335
+ return true;
6336
+ }
6337
+ return false;
6338
+ }
6339
+ function liveExpectStatements(uncommented) {
6340
+ const masked = maskedOutsideStrings(uncommented).split("\n");
6341
+ return accumulateStatements(uncommented.split("\n")).filter((s) => EXPECT_STMT.test(s.stmt) && EXPECT_STMT.test(masked[s.startLine - 1] ?? ""));
6342
+ }
6343
+ function stripLineComment2(ln) {
6344
+ let quote = null;
6345
+ for (let i = 0; i < ln.length; i++) {
6346
+ const c = ln[i];
6347
+ if (quote) {
6348
+ if (c === quote && ln[i - 1] !== "\\")
6349
+ quote = null;
6350
+ continue;
6351
+ }
6352
+ if (c === '"' || c === "'" || c === "`") {
6353
+ quote = c;
6354
+ continue;
6355
+ }
6356
+ if (c === "/" && ln[i + 1] === "/")
6357
+ return ln.slice(0, i);
6358
+ }
6359
+ return ln;
6360
+ }
6361
+ function splitInteractionStatementsWithLines(lines) {
6362
+ const out = [];
6363
+ let depth = 0;
6364
+ let scaffold = false;
6365
+ for (let i = 0; i < lines.length; i++) {
6366
+ const ln = stripLineComment2(lines[i]).trim();
6221
6367
  if (!ln)
6222
6368
  continue;
6223
6369
  const isContinuation = out.length > 0 && (ln.startsWith(".") || depth > 0 && !scaffold);
@@ -6241,7 +6387,7 @@ function testBlockIds(lines) {
6241
6387
  let any = false;
6242
6388
  let block = 0;
6243
6389
  for (let i = 0; i < lines.length; i++) {
6244
- const ln = stripLineComment(lines[i]).trim();
6390
+ const ln = stripLineComment2(lines[i]).trim();
6245
6391
  if (!open) {
6246
6392
  if (!TEST_BLOCK_OPEN.test(ln))
6247
6393
  continue;
@@ -6325,7 +6471,7 @@ function expectSubjectSpan(stmt) {
6325
6471
  }
6326
6472
  return null;
6327
6473
  }
6328
- function resolveSubjectBindings(stmts, bindings, blockOfLine) {
6474
+ function resolveSubjectBindings(stmts, bindings, blockOfLine, boundAt) {
6329
6475
  if (bindings.length === 0)
6330
6476
  return [...stmts];
6331
6477
  return stmts.map((item) => {
@@ -6348,14 +6494,22 @@ function resolveSubjectBindings(stmts, bindings, blockOfLine) {
6348
6494
  subject = `${lead[1]}${found.expr.replace(/;\s*$/, "")}${subject.slice(lead[0].length)}`;
6349
6495
  line = found.line;
6350
6496
  }
6351
- return { ...item, stmt: `${item.stmt.slice(0, span.from)}${subject}${item.stmt.slice(span.to)}` };
6497
+ const out = { ...item, stmt: `${item.stmt.slice(0, span.from)}${subject}${item.stmt.slice(span.to)}` };
6498
+ if (boundAt !== void 0 && line !== item.startLine)
6499
+ boundAt.set(out, line);
6500
+ return out;
6352
6501
  });
6353
6502
  }
6354
6503
  function stripLoginScaffolding(code) {
6355
- const out = [];
6504
+ const lines = code.split("\n");
6505
+ return keptScaffoldingLines(lines).map((i) => lines[i]).join("\n");
6506
+ }
6507
+ function keptScaffoldingLines(lines) {
6508
+ const kept = [];
6356
6509
  let inAuth = false;
6357
6510
  let inInbox = false;
6358
- for (const ln of code.split("\n")) {
6511
+ for (let i = 0; i < lines.length; i++) {
6512
+ const ln = lines[i];
6359
6513
  if (!inAuth && !inInbox && AUTH_START.test(ln)) {
6360
6514
  inAuth = true;
6361
6515
  continue;
@@ -6376,9 +6530,9 @@ function stripLoginScaffolding(code) {
6376
6530
  }
6377
6531
  if (LOGIN_CALL_LINE.test(ln))
6378
6532
  continue;
6379
- out.push(ln);
6533
+ kept.push(i);
6380
6534
  }
6381
- return out.join("\n");
6535
+ return kept;
6382
6536
  }
6383
6537
  function stripCommentsAndLogs(code) {
6384
6538
  return stripCoverageComments(code).split("\n").filter((ln) => !/^\s*console\./.test(ln)).join("\n");
@@ -6387,7 +6541,7 @@ function detectsErrorBannerAssertion(code) {
6387
6541
  const cleaned = stripCommentsAndLogs(stripLoginScaffolding(code));
6388
6542
  const lines = cleaned.split("\n");
6389
6543
  const candidates = [];
6390
- const positive = (stmt) => !NEGATED_ASSERT.test(stmt) && !ABSENCE_MATCHER.test(stmt);
6544
+ const positive = (stmt) => !claimsAbsence(stmt);
6391
6545
  const statements = liveExpectStatements(cleaned).map((s) => s.stmt);
6392
6546
  for (const stmt of statements) {
6393
6547
  if (positive(stmt))
@@ -6516,6 +6670,12 @@ function detectsObservedFailureAsExpected(code) {
6516
6670
  return FAILURE_STATUS_ASSERT.some((re) => re.test(stmt)) || hasFailedListener && FAILED_LIST_ASSERT.test(stmt);
6517
6671
  });
6518
6672
  }
6673
+ function claimsAbsence(stmt) {
6674
+ const negated = NEGATED_ASSERT_NOT.test(stmt);
6675
+ const bare = negated ? stmt.replace(NOT_BEFORE_MATCHER, ".") : stmt;
6676
+ const absenceMatcher = /\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/.test(bare) || VALUE_ABSENCE.test(bare) || OPTION_ABSENCE.test(bare);
6677
+ return negated !== absenceMatcher;
6678
+ }
6519
6679
  function isVacuousErrorAbsence(stmt, presence) {
6520
6680
  if (!ABSENCE_MATCHER.test(stmt))
6521
6681
  return false;
@@ -6524,6 +6684,41 @@ function isVacuousErrorAbsence(stmt, presence) {
6524
6684
  return false;
6525
6685
  return !presence().includes(m[1]);
6526
6686
  }
6687
+ function isVacuousAccountAbsence(stmt, shown, account) {
6688
+ if (!claimsAbsence(stmt))
6689
+ return false;
6690
+ if (!ABSENCE_MATCHER.test(stmt)) {
6691
+ const negated = NEGATED_TEXT.exec(stmt);
6692
+ if (!negated)
6693
+ return false;
6694
+ const read = account.exec(stmt.slice(negated.index));
6695
+ return read !== null && !shown().has(read[0]);
6696
+ }
6697
+ const m = account.exec(stmt);
6698
+ if (!m || shown().has(m[0]))
6699
+ return false;
6700
+ for (let at = endOfEnclosingCall(stmt, m.index + m[0].length); at < stmt.length; at = endOfEnclosingCall(stmt, at)) {
6701
+ SCOPING_STEP.lastIndex = at;
6702
+ if (SCOPING_STEP.test(stmt))
6703
+ return false;
6704
+ }
6705
+ return true;
6706
+ }
6707
+ function endOfEnclosingCall(stmt, from) {
6708
+ let depth = 0;
6709
+ for (let i = from; i < stmt.length; i++) {
6710
+ const c = stmt[i];
6711
+ if (c === "'" || c === '"') {
6712
+ for (i++; i < stmt.length && stmt[i] !== c; i++)
6713
+ if (stmt[i] === "\\")
6714
+ i++;
6715
+ } else if (c === "(")
6716
+ depth++;
6717
+ else if (c === ")" && depth-- === 0)
6718
+ return i + 1;
6719
+ }
6720
+ return stmt.length;
6721
+ }
6527
6722
  function isBareContainerVisibility(stmt) {
6528
6723
  return BARE_CONTAINER_ROLE.test(stmt) && !GET_BY_TEXT.test(stmt) && !HAS_TEXT_FILTER.test(stmt) && !TEXT_CONTENT_MATCHER.test(stmt);
6529
6724
  }
@@ -6531,19 +6726,53 @@ function isChromeTarget(stmt) {
6531
6726
  return CHROME_LOCATOR.test(stmt) && !CONTENT_ROLE.test(stmt) && !/getByText\s*\(/.test(stmt);
6532
6727
  }
6533
6728
  function countsAsContentText(stmt) {
6534
- if (!TEXT_CONTENT_MATCHER.test(stmt))
6729
+ if (!TEXT_CONTENT_MATCHER.test(stmt) || NEGATED_ASSERT.test(stmt))
6535
6730
  return false;
6536
6731
  if (isSymbolOnlyArgument(stmt, TEXT_CONTENT_MATCHER))
6537
6732
  return false;
6538
- if (!isChromeTarget(stmt))
6733
+ return onContentTarget(stmt);
6734
+ }
6735
+ function onContentTarget(stmt) {
6736
+ return !isChromeTarget(stmt) || /getByRole\s*\(\s*['"`]main['"`]/i.test(stmt);
6737
+ }
6738
+ function provesTextRemoved(stmt, presence) {
6739
+ const m = stmt.match(NEGATED_TEXT);
6740
+ if (!m || !onContentTarget(stmt))
6741
+ return false;
6742
+ const text = m[1] ?? m[2];
6743
+ if (text === void 0)
6539
6744
  return true;
6540
- return /getByRole\s*\(\s*['"`]main['"`]/i.test(stmt);
6745
+ if (!/[\p{L}\p{N}]/u.test(text))
6746
+ return false;
6747
+ return !ERROR_TEXT_WORDS.test(text) || presence().includes(text);
6541
6748
  }
6542
6749
  function isDataContentRole(stmt) {
6543
6750
  return CONTENT_ROLE.test(stmt) && !ANNOUNCE_ROLE.test(stmt);
6544
6751
  }
6752
+ function destructuresRequest(code) {
6753
+ let readTo = -1;
6754
+ for (const m of code.matchAll(ASYNC_DESTRUCTURE_OPEN)) {
6755
+ const start = m.index + m[0].length;
6756
+ if (start <= readTo)
6757
+ continue;
6758
+ let end = start;
6759
+ while (end < code.length && code[end] !== "}" && code[end] !== ")")
6760
+ end++;
6761
+ readTo = end;
6762
+ if (end === code.length)
6763
+ return false;
6764
+ if (code[end] !== "}" || !REQUEST_WORD.test(code.slice(start, end)))
6765
+ continue;
6766
+ let close = end + 1;
6767
+ while (close < code.length && /\s/.test(code[close]))
6768
+ close++;
6769
+ if (code[close] === ")")
6770
+ return true;
6771
+ }
6772
+ return false;
6773
+ }
6545
6774
  function isApiTest(code) {
6546
- const usesRequestFixture = /async\s*\(\s*\{[^})]*\brequest\b[^})]*\}\s*\)/.test(code) || /\brequest\.(get|post|put|patch|delete|head|fetch)\s*\(/.test(code);
6775
+ const usesRequestFixture = destructuresRequest(code) || /\brequest\.(get|post|put|patch|delete|head|fetch)\s*\(/.test(code);
6547
6776
  if (!usesRequestFixture)
6548
6777
  return false;
6549
6778
  const drivesUi = /\bpage\.(getBy\w+|locator|click|fill|goto|waitForURL|reload)\s*\(/.test(code) || /\.(click|fill|check|uncheck|selectOption|pressSequentially)\s*\(/.test(code);
@@ -6571,7 +6800,7 @@ function roleNames(s) {
6571
6800
  function interactedControlNames(lines) {
6572
6801
  const names = /* @__PURE__ */ new Set();
6573
6802
  for (const raw of lines) {
6574
- const ln = stripLineComment(raw);
6803
+ const ln = stripLineComment2(raw);
6575
6804
  if (!INTERACT_VERB.test(ln))
6576
6805
  continue;
6577
6806
  for (const n of roleNames(ln))
@@ -6767,6 +6996,14 @@ function balancedArguments(code, from, limit = code.length) {
6767
6996
  }
6768
6997
  return null;
6769
6998
  }
6999
+ function expectCallTail(stmt) {
7000
+ const at = stmt.search(EXPECT_CALL_RE);
7001
+ if (at < 0)
7002
+ return null;
7003
+ const from = stmt.indexOf("(", at) + 1;
7004
+ const args = balancedArguments(stmt, from, MATCHER_ARGS_MAX);
7005
+ return args === null ? null : stmt.slice(from + args.length).trimStart();
7006
+ }
6770
7007
  function matcherArgumentsAreConstant(body, from) {
6771
7008
  MATCHER_OPEN.lastIndex = from;
6772
7009
  const opened = MATCHER_OPEN.exec(body);
@@ -6802,34 +7039,72 @@ function hasOnlyConstantAssertions(code) {
6802
7039
  const { constant, real } = constantAssertions(code);
6803
7040
  return constant.length > 0 && real === 0;
6804
7041
  }
7042
+ function envValueKey(name) {
7043
+ return `\0env:${name}`;
7044
+ }
6805
7045
  function enteredValues(stmts) {
6806
7046
  const values = /* @__PURE__ */ new Map();
6807
7047
  const navLines = [];
7048
+ const accountConsts = /* @__PURE__ */ new Set();
6808
7049
  for (const { stmt, startLine } of stmts) {
6809
7050
  if (PAGE_NAV.test(stmt))
6810
7051
  navLines.push(startLine);
7052
+ for (const c of stmt.matchAll(ACCOUNT_CONST))
7053
+ accountConsts.add(c[1].replace(/\$/g, "\\$"));
6811
7054
  let m;
6812
7055
  ENTERED_VALUE.lastIndex = 0;
6813
- while (m = ENTERED_VALUE.exec(stmt)) {
6814
- const value = (m[1] ?? m[2] ?? m[3]).toLowerCase();
6815
- if (!values.has(value))
6816
- values.set(value, startLine);
6817
- }
7056
+ const add = (value) => {
7057
+ const lines = values.get(value);
7058
+ if (!lines)
7059
+ values.set(value, [startLine]);
7060
+ else if (lines[lines.length - 1] !== startLine)
7061
+ lines.push(startLine);
7062
+ };
7063
+ while (m = ENTERED_VALUE.exec(stmt))
7064
+ add((m[1] ?? m[2] ?? m[3]).toLowerCase());
7065
+ ENTERED_ENV.lastIndex = 0;
7066
+ while (m = ENTERED_ENV.exec(stmt))
7067
+ add(envValueKey(m[1] ?? m[2] ?? m[3] ?? m[4]));
6818
7068
  }
6819
- return { values, navLines };
7069
+ const consts = accountConsts.size > 0 ? `|(?<![\\w$.])(?:${[...accountConsts].join("|")})(?![\\w$])` : "";
7070
+ return { values, navLines, account: new RegExp(`${ACCOUNT_ENV}${consts}`) };
6820
7071
  }
6821
- function isEnteredValueEcho(stmt, entered, startLine) {
7072
+ function countBelow(lines, line) {
7073
+ let lo = 0;
7074
+ let hi = lines.length;
7075
+ while (lo < hi) {
7076
+ const mid = lo + hi >> 1;
7077
+ if (lines[mid] < line)
7078
+ lo = mid + 1;
7079
+ else
7080
+ hi = mid;
7081
+ }
7082
+ return lo;
7083
+ }
7084
+ function enteredLinesOf(stmt, entered) {
6822
7085
  const m = stmt.match(/\.toHaveValue\s*\(\s*(['"`])((?:(?!\1).)+)\1/);
6823
- if (!m)
7086
+ if (m)
7087
+ return entered.values.get(m[2].toLowerCase());
7088
+ const env = stmt.match(HAVE_VALUE_ENV);
7089
+ return env ? entered.values.get(envValueKey(env[1] ?? env[2])) : void 0;
7090
+ }
7091
+ function typedSinceLastNav(stmt, entered, startLine, lines = enteredLinesOf(stmt, entered)) {
7092
+ if (!lines || startLine === void 0)
6824
7093
  return false;
6825
- const enteredAt = entered.values.get(m[2].toLowerCase());
6826
- if (enteredAt === void 0)
7094
+ const before = countBelow(lines, startLine);
7095
+ if (before === 0)
6827
7096
  return false;
6828
- if (startLine === void 0)
6829
- return true;
6830
- return !entered.navLines.some((n) => n > enteredAt && n < startLine);
7097
+ return countBelow(entered.navLines, startLine) === countBelow(entered.navLines, lines[before - 1] + 1);
6831
7098
  }
6832
- function isAnyValueMatcher(stmt) {
7099
+ function isEnteredValueEcho(stmt, entered, startLine) {
7100
+ const lines = enteredLinesOf(stmt, entered);
7101
+ if (!lines)
7102
+ return false;
7103
+ if (startLine === void 0 || lines[0] >= startLine)
7104
+ return true;
7105
+ return typedSinceLastNav(stmt, entered, startLine, lines);
7106
+ }
7107
+ function isAnyValueMatcher(stmt) {
6833
7108
  if (/\.not\.toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/.test(stmt))
6834
7109
  return true;
6835
7110
  const m = stmt.match(/\.toHaveValue\s*\(\s*\/((?:\\.|[^/\\]){1,300})\/[a-z]*/);
@@ -6917,6 +7192,49 @@ function hasAttributeTransition(stmts) {
6917
7192
  return true;
6918
7193
  return false;
6919
7194
  }
7195
+ function comparesWithItself(subject, tail) {
7196
+ const m = SELF_COMPARE_MATCHER.exec(tail);
7197
+ if (!m)
7198
+ return false;
7199
+ const args = balancedArguments(tail, m[0].length, MATCHER_ARGS_MAX);
7200
+ if (args === null)
7201
+ return false;
7202
+ const squash2 = (x) => x.replace(/^\s*await\s+/, "").replace(/\s+/g, "");
7203
+ const expected = squash2(args);
7204
+ return expected === squash2(m[1] === "toHaveCount" ? `${subject}.count()` : subject);
7205
+ }
7206
+ function asPlainAssertion(s) {
7207
+ return plainAssertion(s, false);
7208
+ }
7209
+ function plainAssertion(s, keepSelfCompare) {
7210
+ const { stmt } = s;
7211
+ if (COUNT_AT_LEAST_ZERO.test(stmt))
7212
+ return [];
7213
+ const subject = extractExpectSubject(stmt);
7214
+ if (subject === null)
7215
+ return [s];
7216
+ const plain = subject.replace(/^await\s+/, "").trim();
7217
+ const subjectAt = stmt.indexOf(subject, stmt.search(EXPECT_CALL_RE));
7218
+ const tail = (stmt.startsWith("expect.poll", stmt.search(EXPECT_CALL_RE)) ? expectCallTail(stmt) : null) ?? stmt.slice(subjectAt + subject.length).trimStart();
7219
+ const as = (rewritten) => [{ ...s, stmt: rewritten }];
7220
+ if (LOCATOR_EXPR.test(plain) && !LOCATOR_VALUE_READ.test(plain) && ALWAYS_TRUE_ON_OBJECT.test(tail))
7221
+ return [];
7222
+ if (DEFINED_VALUE_READ.test(plain) && ALWAYS_DEFINED.test(tail))
7223
+ return [];
7224
+ if (!keepSelfCompare && comparesWithItself(plain, tail))
7225
+ return [];
7226
+ const visible = /^(.+)\.\s*isVisible\s*\(\s*\)$/s.exec(plain);
7227
+ if (visible && IS_VISIBLE_TRUE.test(tail))
7228
+ return as(`await expect(${visible[1]}).toBeVisible();`);
7229
+ if (/^page\s*\.\s*(?:url|title)\s*\(\s*\)$/.test(plain) && PAGE_VALUE_MATCHER.test(tail)) {
7230
+ const matcher = plain.includes("url") ? "toHaveURL" : "toHaveTitle";
7231
+ return as(`await expect(page)${tail.replace(/^\)\s*\.\s*\w+\s*\(/, `.${matcher}(`)}`);
7232
+ }
7233
+ return [s];
7234
+ }
7235
+ function assertionStrengthHoistedReadsOn(value = typeof process !== "undefined" ? process.env?.ASSERTION_STRENGTH_HOISTED_READS : void 0) {
7236
+ return /^(1|true|on)$/i.test((value ?? "").trim());
7237
+ }
6920
7238
  function creditsOutcomeAlways(stmt, entered, startLine) {
6921
7239
  if (!OUTCOME_ALWAYS.some((re) => re.test(stmt)))
6922
7240
  return false;
@@ -6949,6 +7267,22 @@ function successNoticeQuote(stmt) {
6949
7267
  }
6950
7268
  return null;
6951
7269
  }
7270
+ function namedPagePath(arg) {
7271
+ const text = arg.trim();
7272
+ let path;
7273
+ const quoted = /^(['"`])([^'"`]*)\1$/.exec(text);
7274
+ const regex = quoted ? null : /^\/((?:\\.|[^/\\])+)\/[a-z]*$/.exec(text);
7275
+ if (quoted) {
7276
+ path = quoted[2].replace(/^\$\{[^}]*\}/, "").replace(/^\*\*/, "").replace(/^[a-z][a-z\d+.-]*:\/\/[^/]*/i, "").replace(/\*\*$/, "");
7277
+ } else if (regex) {
7278
+ path = regex[1].replace(/^\^/, "").replace(/^\.\*/, "").replace(/\$$/, "").replace(/\\([/.-])/g, "$1");
7279
+ } else {
7280
+ return null;
7281
+ }
7282
+ if (!PLAIN_PAGE_PATH.test(path))
7283
+ return null;
7284
+ return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
7285
+ }
6952
7286
  function provesStoredControlValue(stmt, startLine, persistence) {
6953
7287
  if (!persistence || startLine === void 0)
6954
7288
  return false;
@@ -6975,7 +7309,64 @@ function reopenedAfterSave(startLine, persistence, reopens = persistence.reloadL
6975
7309
  }
6976
7310
  return lo < reopens.length && reopens[lo] < startLine;
6977
7311
  }
6978
- function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine, persistence) {
7312
+ function actsBetween(withLines, from, to) {
7313
+ let acts = actLinesOf.get(withLines);
7314
+ if (acts === void 0) {
7315
+ acts = withLines.filter((s) => ACTS.test(s.stmt)).map((s) => s.startLine).sort((a, b) => a - b);
7316
+ actLinesOf.set(withLines, acts);
7317
+ }
7318
+ let lo = 0;
7319
+ let hi = acts.length;
7320
+ while (lo < hi) {
7321
+ const mid = lo + hi >> 1;
7322
+ if (acts[mid] <= from)
7323
+ lo = mid + 1;
7324
+ else
7325
+ hi = mid;
7326
+ }
7327
+ return lo < acts.length && acts[lo] < to;
7328
+ }
7329
+ function signInProofWindow(stmts, withLines, lines, blockIds) {
7330
+ if (blockIds === null)
7331
+ return void 0;
7332
+ const testBodies2 = /* @__PURE__ */ new Set();
7333
+ for (let i = 0; i < lines.length; i++) {
7334
+ const id = blockIds[i];
7335
+ if (id > 0 && (i === 0 || blockIds[i - 1] !== id) && TEST_BODY_OPEN.test(stripLineComment2(lines[i]).trim()))
7336
+ testBodies2.add(id);
7337
+ }
7338
+ const blockOf = (i) => blockIds[withLines[i].startLine - 1];
7339
+ const passwordAt = stmts.findIndex((stmt, i) => testBodies2.has(blockOf(i)) && ENV_PASSWORD_ENTRY.test(stmt));
7340
+ if (passwordAt < 0)
7341
+ return void 0;
7342
+ const body = blockOf(passwordAt);
7343
+ let clickAt = passwordAt + 1;
7344
+ while (clickAt < stmts.length && FORM_INPUT.test(stmts[clickAt]) && !ANY_CLICK.test(stmts[clickAt]))
7345
+ clickAt++;
7346
+ if (clickAt >= stmts.length || blockOf(clickAt) !== body || !ANY_CLICK.test(stmts[clickAt]) || NAV_LOCATOR.test(stmts[clickAt]))
7347
+ return void 0;
7348
+ let end = clickAt + 1;
7349
+ for (; end < stmts.length && blockOf(end) === body; end++) {
7350
+ if (CLOSERS_ONLY.test(stmts[end]))
7351
+ continue;
7352
+ if (!ASSERT_OR_WAIT.test(stmts[end]) || ACTS.test(stmts[end]))
7353
+ return void 0;
7354
+ }
7355
+ return { after: withLines[clickAt].startLine, until: end < stmts.length ? withLines[end].startLine : Number.POSITIVE_INFINITY };
7356
+ }
7357
+ function isAccountControlCheck(stmt) {
7358
+ if (!VISIBILITY_MATCHER.test(stmt) || NEGATED_ASSERT.test(stmt) || OPTION_ABSENCE.test(stmt))
7359
+ return false;
7360
+ const role = ACCOUNT_CONTROL_ROLE.exec(stmt);
7361
+ if (!role)
7362
+ return false;
7363
+ const rest = stmt.slice(role.index + role[0].length, role.index + role[0].length + 300);
7364
+ const close = rest.indexOf("}");
7365
+ const options = close < 0 ? rest : rest.slice(0, close);
7366
+ const name = options.match(/(?:^|[\s,])name\s*:\s*/);
7367
+ return name !== null && ACCOUNT_EMAIL_ENV.test(options.slice(name.index + name[0].length));
7368
+ }
7369
+ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine, persistence, signedIn) {
6979
7370
  if (mutated && hasAttributeTransition(stmts))
6980
7371
  return { kind: "durable" };
6981
7372
  const notices = [];
@@ -6985,11 +7376,25 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
6985
7376
  };
6986
7377
  let presenceText;
6987
7378
  const presence = () => presenceText ??= stmts.filter((o) => !ABSENCE_MATCHER.test(o.stmt) && !NEGATED_ASSERT.test(o.stmt)).map((o) => o.stmt).join("\n");
7379
+ let shownAccounts;
7380
+ const shown = () => {
7381
+ if (shownAccounts)
7382
+ return shownAccounts;
7383
+ const ref = new RegExp(entered.account.source, "g");
7384
+ return shownAccounts = new Set(stmts.filter((o) => !claimsAbsence(o.stmt)).flatMap((o) => Array.from(o.stmt.matchAll(ref), (m) => m[0])));
7385
+ };
6988
7386
  for (const { stmt, startLine } of stmts) {
6989
7387
  if (firstMutLine !== void 0 && startLine !== void 0 && startLine < firstMutLine)
6990
7388
  continue;
6991
7389
  if (isVacuousErrorAbsence(stmt, presence))
6992
7390
  continue;
7391
+ if (COUNT_ONE.test(stmt) && quotedAssertedStrings(stmt).some((t) => clicked.has(t.toLowerCase())))
7392
+ continue;
7393
+ if (isVacuousAccountAbsence(stmt, shown, entered.account))
7394
+ continue;
7395
+ if (signedIn && startLine !== void 0 && startLine > signedIn.after && startLine < signedIn.until && isAccountControlCheck(stmt)) {
7396
+ return { kind: "durable" };
7397
+ }
6993
7398
  if (creditsOutcomeAlways(stmt, entered, startLine))
6994
7399
  return { kind: "durable" };
6995
7400
  if (mutated && !NEGATED_ASSERT.test(stmt) && MESSAGE_SURFACE.test(stmt) && OUTCOME_TEXT.test(assertedText(stmt))) {
@@ -6999,12 +7404,15 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
6999
7404
  else
7000
7405
  return { kind: "durable" };
7001
7406
  }
7002
- if (STATE_MATCHER.test(stmt) && (persistence && startLine !== void 0 ? reopenedAfterSave(startLine, persistence, persistence.reloadCalls) : reloaded))
7407
+ const retyped = typedSinceLastNav(stmt, entered, startLine);
7408
+ if (!retyped && STATE_MATCHER.test(stmt) && (persistence && startLine !== void 0 ? reopenedAfterSave(startLine, persistence, persistence.reloadCalls) : reloaded))
7003
7409
  return { kind: "durable" };
7004
- if (provesStoredControlValue(stmt, startLine, persistence))
7410
+ if (!retyped && provesStoredControlValue(stmt, startLine, persistence))
7005
7411
  return { kind: "durable" };
7006
7412
  if (countsAsContentText(stmt))
7007
7413
  return { kind: "durable" };
7414
+ if (provesTextRemoved(stmt, presence))
7415
+ return { kind: "durable" };
7008
7416
  if (mutated && VISIBILITY_MATCHER.test(stmt)) {
7009
7417
  const revealsText = GET_BY_TEXT.test(stmt) || HAS_TEXT_FILTER.test(stmt) || CONTENT_ROLE.test(stmt);
7010
7418
  if (revealsText && !isChromeTarget(stmt)) {
@@ -7068,10 +7476,57 @@ function noticeOnlyExemption(clicked, noticeText, interactionStmts) {
7068
7476
  function detectOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine) {
7069
7477
  return classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine).kind !== "none";
7070
7478
  }
7479
+ function clickLabel(stmt) {
7480
+ let label = null;
7481
+ for (const m of stmt.matchAll(CONTROL_LABEL))
7482
+ label = m[2] ?? m[3] ?? null;
7483
+ return label;
7484
+ }
7485
+ function teardownLines(lines) {
7486
+ const out = new Array(lines.length).fill(false);
7487
+ let open = false;
7488
+ let depth = 0;
7489
+ for (let i = 0; i < lines.length; i++) {
7490
+ const ln = stripLineComment2(lines[i]).trim();
7491
+ let from = 0;
7492
+ if (!open) {
7493
+ const m = TEARDOWN_OPEN.exec(ln);
7494
+ if (!m)
7495
+ continue;
7496
+ open = true;
7497
+ depth = 0;
7498
+ from = m.index;
7499
+ }
7500
+ out[i] = true;
7501
+ let closed = false;
7502
+ let quote = null;
7503
+ for (let k = from; k < ln.length; k++) {
7504
+ const ch = ln[k];
7505
+ if (quote !== null) {
7506
+ if (ch === "\\")
7507
+ k++;
7508
+ else if (ch === quote)
7509
+ quote = null;
7510
+ continue;
7511
+ }
7512
+ if (ch === "'" || ch === '"' || ch === "`")
7513
+ quote = ch;
7514
+ else if (ch === "{")
7515
+ depth++;
7516
+ else if (ch === "}") {
7517
+ depth--;
7518
+ closed = true;
7519
+ }
7520
+ }
7521
+ if (depth <= 0 && closed)
7522
+ open = false;
7523
+ }
7524
+ return out;
7525
+ }
7071
7526
  function validateAssertionStrength(code, opts = {}) {
7072
7527
  const body = stripLoginScaffolding(code);
7073
7528
  const lines = body.split("\n");
7074
- const stmts = liveExpectStatements(stripCoverageComments(body));
7529
+ const stmts = liveExpectStatements(stripCoverageComments(body)).flatMap(asPlainAssertion);
7075
7530
  const base = (extra) => ({
7076
7531
  apiTest: false,
7077
7532
  interacted: false,
@@ -7111,7 +7566,13 @@ function validateAssertionStrength(code, opts = {}) {
7111
7566
  const interactionStmts = inlineLocatorBindings(interactionStmtsRaw);
7112
7567
  const blockIds = testBlockIds(lines);
7113
7568
  const blockOfLine = (line) => blockIds?.[line - 1] ?? 0;
7114
- const checks = resolveSubjectBindings(stmts, scopedLocatorBindings(interactionStmtsWithLines, blockOfLine), blockOfLine);
7569
+ const hoistedReads = assertionStrengthHoistedReadsOn();
7570
+ const boundAt = hoistedReads ? /* @__PURE__ */ new Map() : void 0;
7571
+ const resolved = resolveSubjectBindings(stmts, scopedLocatorBindings(interactionStmtsWithLines, blockOfLine), blockOfLine, boundAt);
7572
+ const checks = boundAt === void 0 ? resolved : resolved.flatMap((s) => {
7573
+ const from = boundAt.get(s);
7574
+ return plainAssertion(s, from !== void 0 && actsBetween(interactionStmtsWithLines, from, s.startLine));
7575
+ });
7115
7576
  const insideTest = linesInsideTestBlocks(lines);
7116
7577
  const inTestOrder = (i) => insideTest === null || insideTest[interactionStmtsWithLines[i].startLine - 1] === true;
7117
7578
  let mutated = opts.interactionPerformed === true;
@@ -7121,9 +7582,34 @@ function validateAssertionStrength(code, opts = {}) {
7121
7582
  let navClicks = 0;
7122
7583
  const persistence = { mutLines: [], reloadLines: [], reloadCalls: [] };
7123
7584
  const openedPages = /* @__PURE__ */ new Set();
7585
+ let checkedPage = null;
7586
+ const savedOnCheckedPage = /* @__PURE__ */ new Set();
7587
+ const saveAt = (i, at) => {
7588
+ lastMutIdx = i;
7589
+ if (!inTestOrder(i))
7590
+ return;
7591
+ persistence.mutLines.push(at);
7592
+ if (firstMutIdx < 0)
7593
+ firstMutIdx = i;
7594
+ if (checkedPage !== null)
7595
+ savedOnCheckedPage.add(checkedPage);
7596
+ };
7597
+ const lastActionProof = opts.lastActionProof ?? "off";
7598
+ const teardown = lastActionProof === "off" ? null : teardownLines(lines);
7599
+ const acts = { last: null, prev: null, count: 0 };
7600
+ const noteAct = (i, at, kind) => {
7601
+ if (teardown === null || teardown[at - 1] || !inTestOrder(i))
7602
+ return;
7603
+ if (kind !== "enter")
7604
+ acts.count++;
7605
+ acts.prev = acts.last;
7606
+ acts.last = { i, kind };
7607
+ };
7124
7608
  for (let i = 0; i < interactionStmts.length; i++) {
7125
7609
  const ln = interactionStmts[i];
7126
7610
  const at = interactionStmtsWithLines[i].startLine;
7611
+ if (teardown !== null && acts.last?.kind === "fill" && ENTER_PRESS.test(ln))
7612
+ noteAct(i, at, "enter");
7127
7613
  if (RELOAD.test(ln)) {
7128
7614
  persistence.reloadLines.push(at);
7129
7615
  persistence.reloadCalls.push(at);
@@ -7131,32 +7617,31 @@ function validateAssertionStrength(code, opts = {}) {
7131
7617
  const opened = ln.match(/\.goto\s*\(\s*([^,)]+)/);
7132
7618
  if (opened) {
7133
7619
  const page = opened[1].trim();
7134
- if (openedPages.has(page))
7620
+ const path = namedPagePath(page);
7621
+ if (openedPages.has(page) || path !== null && savedOnCheckedPage.has(path))
7135
7622
  persistence.reloadLines.push(at);
7136
- else
7137
- openedPages.add(page);
7138
- }
7623
+ openedPages.add(page);
7624
+ checkedPage = null;
7625
+ }
7626
+ if (/\bpage\s*\.\s*go(?:Back|Forward)\s*\(/.test(ln))
7627
+ checkedPage = null;
7628
+ const checked = URL_CHECK_ARG.exec(ln);
7629
+ if (checked)
7630
+ checkedPage = namedPagePath(checked[1] ?? checked[2]);
7139
7631
  if (FORM_INPUT.test(ln)) {
7140
7632
  mutated = true;
7141
- lastMutIdx = i;
7142
- if (inTestOrder(i)) {
7143
- persistence.mutLines.push(at);
7144
- if (firstMutIdx < 0)
7145
- firstMutIdx = i;
7146
- }
7633
+ saveAt(i, at);
7634
+ noteAct(i, at, "fill");
7147
7635
  continue;
7148
7636
  }
7149
7637
  if (ANY_CLICK.test(ln)) {
7150
7638
  if (NAV_LOCATOR.test(ln)) {
7151
7639
  navClicks++;
7640
+ checkedPage = null;
7152
7641
  } else {
7153
7642
  mutated = true;
7154
- lastMutIdx = i;
7155
- if (inTestOrder(i)) {
7156
- persistence.mutLines.push(at);
7157
- if (firstMutIdx < 0)
7158
- firstMutIdx = i;
7159
- }
7643
+ saveAt(i, at);
7644
+ noteAct(i, at, "click");
7160
7645
  }
7161
7646
  }
7162
7647
  }
@@ -7189,9 +7674,28 @@ function validateAssertionStrength(code, opts = {}) {
7189
7674
  };
7190
7675
  }
7191
7676
  const orderedPersistence = firstMutLine !== void 0 ? persistence : void 0;
7192
- const proof = classifyOutcomeProof(checks, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence);
7677
+ const signedIn = signInProofWindow(interactionStmts, interactionStmtsWithLines, lines, blockIds);
7678
+ const proof = classifyOutcomeProof(checks, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence, signedIn);
7193
7679
  if (proof.kind === "durable") {
7194
7680
  const signals2 = base({ interacted, mutated, navOnly, hasOutcomeProof: true, terminal });
7681
+ const { last, prev } = acts;
7682
+ 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])))) {
7683
+ const lastLine = interactionStmtsWithLines[last.i].startLine;
7684
+ const clicked = clickedControlNames(interactionStmts);
7685
+ const after = classifyOutcomeProof(checks, mutated, reloaded, clicked, entered, lastLine, orderedPersistence, signedIn);
7686
+ const unproven = after.kind === "none" || after.kind === "notice" && noticeOnlyExemption(clicked, after.noticeText, interactionStmts) === null;
7687
+ if (unproven && lastActionProof === "on") {
7688
+ const line = (keptScaffoldingLines(code.split("\n"))[lastLine - 1] ?? lastLine - 1) + 1;
7689
+ return {
7690
+ ok: false,
7691
+ severity: "block",
7692
+ reason: `the last save (line ${line}) has no outcome check after it (the earlier check proves only the earlier action)`,
7693
+ signals: { ...signals2, lastActionUnproven: true }
7694
+ };
7695
+ }
7696
+ if (unproven)
7697
+ signals2.lastActionUnproven = true;
7698
+ }
7195
7699
  return { ok: true, severity: "none", reason: "proves an outcome (count / diff / value / revealed content)", signals: signals2 };
7196
7700
  }
7197
7701
  if (mutated && proof.kind === "notice") {
@@ -7240,7 +7744,7 @@ function validateAssertionStrength(code, opts = {}) {
7240
7744
  signals
7241
7745
  };
7242
7746
  }
7243
- const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && URL_PROOF.test(s));
7747
+ const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && isUrlProof(s));
7244
7748
  if (urlProofAfter) {
7245
7749
  return {
7246
7750
  ok: false,
@@ -7277,7 +7781,7 @@ function validateAssertionStrength(code, opts = {}) {
7277
7781
  signals
7278
7782
  };
7279
7783
  }
7280
- var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, IN_FLIGHT_TEXT, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD;
7784
+ 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;
7281
7785
  var init_assertion_strength = __esm({
7282
7786
  "../shared/dist/assertion-strength.js"() {
7283
7787
  "use strict";
@@ -7286,6 +7790,7 @@ var init_assertion_strength = __esm({
7286
7790
  init_assertion_proof();
7287
7791
  init_auth_handoff_coverage();
7288
7792
  init_expect_call();
7793
+ init_auth_plan();
7289
7794
  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;
7290
7795
  OUTCOME_ALWAYS = [
7291
7796
  /\.toHaveCount\s*\(/,
@@ -7302,8 +7807,8 @@ var init_assertion_strength = __esm({
7302
7807
  // form field value
7303
7808
  /\.toMatch\s*\(/,
7304
7809
  // string/regex content match (e.g. textContent() vs /[2-9] selected/)
7305
- /\.toBeDisabled\s*\(/,
7306
- // a control became DISABLED — a gating/validation outcome
7810
+ /\.(?<!\.not\s*\.)\s*toBeDisabled\s*\(/,
7811
+ // a control became DISABLED — a gating/validation outcome (not.toBeDisabled is the toBeEnabled twin)
7307
7812
  /\.not\.toBeVisible\s*\(/,
7308
7813
  /\.toBeHidden\s*\(/
7309
7814
  // something the action removed/closed (disappearance proof)
@@ -7311,12 +7816,16 @@ var init_assertion_strength = __esm({
7311
7816
  FORM_INPUT = /\.(fill|selectOption|setInputFiles|check|uncheck|setChecked|pressSequentially)\s*\(|keyboard\.type\s*\(/;
7312
7817
  ANY_CLICK = /\.click\s*\(/;
7313
7818
  NAV_LOCATOR = /getByRole\s*\(\s*['"`](?:link|menuitem)['"`]/i;
7314
- URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(/;
7819
+ 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*\(/;
7820
+ POLL_OPEN = /\bexpect\s*\.\s*poll\s*\(/g;
7821
+ POLL_PAGE_URL = /^page\s*\.\s*url\s*\(\s*\)/;
7822
+ POLL_PAGE_URL_ONLY = /^page\s*\.\s*url\s*\(\s*\)$/;
7823
+ POLL_CALLS_MAX = 8;
7315
7824
  RELOAD = /\.reload\s*\(/;
7316
7825
  EXPECT_STMT = /\bexpect\s*[.(]/;
7317
7826
  BLOCK_SCAFFOLD2 = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
7318
7827
  TEST_BLOCK_OPEN = /^(?:test|it)(?:\.(?:only|skip|fixme|slow|serial|describe\.serial))?\s*\(|^(?:test\.)?(?:beforeEach|beforeAll)\s*\(/;
7319
- LOCATOR_BINDING = /^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(.+?(?:getByRole|getByText|getByLabel|getByPlaceholder|getByTestId|getByAltText|getByTitle|locator)\s*\(.+)$/;
7828
+ 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*\(.+)$/;
7320
7829
  EXPECT_OPEN = /\bexpect(?:\.soft)?\s*\(/;
7321
7830
  STATE_MATCHER = /\.(toBeChecked|toBeVisible|toHaveValue|toBeDisabled|toBeEnabled)\s*\(/;
7322
7831
  CHROME_LOCATOR = /getByRole\s*\(\s*['"`](?:heading|navigation|banner|main|contentinfo|complementary|region|search|tablist|tab|button|link|menuitem|textbox|searchbox|combobox|spinbutton)['"`]/i;
@@ -7336,16 +7845,17 @@ var init_assertion_strength = __esm({
7336
7845
  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;
7337
7846
  AUTH_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_AUTH/;
7338
7847
  AUTH_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_AUTH/;
7339
- LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*;?\s*$/;
7848
+ LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*(?:;\s*)?$/;
7340
7849
  INBOX_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_INBOX/;
7341
7850
  INBOX_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_INBOX/;
7342
7851
  NEGATED_ASSERT = /\.not\s*\.\s*to[A-Z]\w*|\.toBeHidden\s*\(/;
7852
+ NEGATED_ASSERT_NOT = /\.not\s*\.\s*to[A-Z]/;
7343
7853
  CONST_CHAIN_MAX_DEPTH = 5;
7344
7854
  TYPED_INPUT = /\.(fill|type|pressSequentially|setInputFiles)\s*\(/;
7345
7855
  TITLE_LINE = /^\s*(?:test|describe)(?:\.\w+)?\s*\(/;
7346
7856
  FAILURE_STATUS_ASSERT = [
7347
7857
  /\.status\s*\(\s*\)[^;]{0,200}\.(?:toBeGreaterThanOrEqual|toBeGreaterThan)\s*\(\s*(?:399|[45]\d\d)\b/,
7348
- /\bexpect\s*\(\s*[^;]{0,200}\bstatus\s*(?:\(\s*\))?\s*>=?\s*(?:399|[45]\d\d)\b[^;]{0,200}\)\s*\.\s*(?:toBe\s*\(\s*true|toBeTruthy)/,
7858
+ /\bexpect\s*\(\s*[^;]{0,200}\bstatus\s*(?:\(\s*\)\s*)?>=?\s*(?:399|[45]\d\d)\b[^;]{0,200}\)\s*\.\s*(?:toBe\s*\(\s*true|toBeTruthy)/,
7349
7859
  /\bexpect\s*\([^;]{0,200}\.ok\s*\(\s*\)\s*\)\s*\.\s*(?:toBe\s*\(\s*false\s*\)|toBeFalsy\s*\()/
7350
7860
  ];
7351
7861
  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;
@@ -7355,8 +7865,15 @@ var init_assertion_strength = __esm({
7355
7865
  CATCH_ALL_PATTERN = /^(['"`])\*\*(?:\/\*)?\1$|^\/\.\*\/[a-z]*$/;
7356
7866
  GUARD_HANDLER = /\bMUTATING\.has\s*\(|blockedbyclient|Read-only guard/;
7357
7867
  ABSENCE_MATCHER = /\.not\.toBeVisible\s*\(|\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/;
7868
+ VALUE_ABSENCE = /\.(?:count\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*0\s*\)|toBeLessThan\s*\(\s*1\s*\))|isVisible\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*false\s*\)|toBeFalsy\s*\()|isHidden\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*true\s*\)|toBeTruthy\s*\())/;
7869
+ OPTION_ABSENCE = /\.(?:toBeVisible\s*\(\s*\{[^{}()]{0,200}\bvisible|toBeAttached\s*\(\s*\{[^{}()]{0,200}\battached)\s*:\s*false\b/;
7870
+ NOT_BEFORE_MATCHER = /\.not\s*\.\s*(?=to[A-Z])/g;
7358
7871
  ERROR_TEXT_WORDS = /error|fail|wrong|try again|unable|could ?n.?t|can.?t|problem|invalid|unavailable/i;
7359
7872
  TEXT_REGEX_ARG = /(?:getByText|hasText|getByRole\([^)]{0,200}name)\s*[:(]\s*(\/(?:\\.|[^/\\\n])+\/[a-z]*)/;
7873
+ SCOPING_STEP = /(?:\s*\.\s*(?:first|last|nth)\s*\([^()]*\))*\s*\.\s*(?:getBy[A-Z]\w*|locator|filter)\s*\(/y;
7874
+ NEGATED_TEXT = /\.not\s*\.\s*(?:toHaveText|toContainText)\s*\(\s*(?:['"`]([^'"`]*)['"`]|\/((?:\\.|[^/\\\n])+)\/)?/;
7875
+ ASYNC_DESTRUCTURE_OPEN = /async\s*\(\s*\{/g;
7876
+ REQUEST_WORD = /\brequest\b/;
7360
7877
  ROLE_NAME_RE = /getByRole\s*\(\s*['"`][a-z]+['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
7361
7878
  INTERACT_VERB = /\.(click|fill|check|uncheck|setChecked|selectOption|pressSequentially)\s*\(/;
7362
7879
  ROLE_SIG_RE = /getByRole\s*\(\s*['"`]([a-z]+)['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
@@ -7377,15 +7894,55 @@ var init_assertion_strength = __esm({
7377
7894
  MATCHER_OPEN = /\s*(?:\.\s*(?:not|resolves|rejects)\s*)*\.\s*[A-Za-z_$][\w$]*\s*\(/y;
7378
7895
  ENTERED_VALUE = /\.(?:fill|pressSequentially)\s*\(\s*['"`]([^'"`]+)['"`]|keyboard\.type\s*\(\s*['"`]([^'"`]+)['"`]|\.selectOption\s*\(\s*(?:\[\s*)?(?:\{\s*(?:label|value)\s*:\s*)?['"`]([^'"`]+)['"`]/g;
7379
7896
  PAGE_NAV = /\.(?:goto|reload)\s*\(/;
7897
+ ENV_READ = String.raw`process\.env(?:(?:\?\.|\.)([A-Za-z_]\w*)|(?:\?\.)?\[\s*['"]([A-Za-z_]\w*)['"]\s*\])`;
7898
+ ENTERED_ENV = new RegExp(String.raw`\.(?:fill|pressSequentially)\s*\(\s*${ENV_READ}|keyboard\.type\s*\(\s*${ENV_READ}`, "g");
7899
+ HAVE_VALUE_ENV = new RegExp(String.raw`\.toHaveValue\s*\(\s*${ENV_READ}`);
7900
+ ACCOUNT_KEYS = TEST_USER_ENV_KEYS.join("|");
7901
+ ACCOUNT_ENV = `process\\.env(?:\\??\\.(?:${ACCOUNT_KEYS})\\b|(?:\\?\\.)?\\[\\s*['"\`](?:${ACCOUNT_KEYS})['"\`]\\s*\\])`;
7902
+ ACCOUNT_CONST = new RegExp(`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*(?::\\s*string\\s*)?=\\s*${ACCOUNT_ENV}`, "g");
7380
7903
  IN_FLIGHT_TEXT = /^(?:(?:sending|saving|loading|searching|checking|processing|submitting|preparing|updating|please wait|working|one moment)\b[^.]{0,40}\.{2,3}|\.{3}|…)$/i;
7904
+ LOCATOR_VALUE_READ = /\.(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts|evaluate|evaluateAll|boundingBox|all)\s*\(/;
7905
+ LOCATOR_EXPR = /^(?:this\.)?page\s*\.\s*(?:getBy\w+|locator)\s*\(/;
7906
+ ALWAYS_TRUE_ON_OBJECT = /^\)\s*\.\s*(?:toBeTruthy|toBeDefined|not\s*\.\s*(?:toBeNull|toBeUndefined|toBeFalsy))\s*\(/;
7907
+ COUNT_AT_LEAST_ZERO = /\.(?:count\s*\(\s*\)|length)[\s)]{0,10}\.(?:toBeGreaterThanOrEqual\s*\(\s*0\s*\)|toBeGreaterThan\s*\(\s*-\s*1\s*\))/;
7908
+ DEFINED_VALUE_READ = /\.\s*(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|title|url|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts)\s*\([^()]*\)$/;
7909
+ ALWAYS_DEFINED = /^\)\s*\.\s*(?:toBeDefined|not\s*\.\s*toBeUndefined)\s*\(/;
7910
+ SELF_COMPARE_MATCHER = /^\)\s*\.\s*(toBe|toEqual|toStrictEqual|toHaveCount)\s*\(/;
7911
+ IS_VISIBLE_TRUE = /^\)*\s*\.\s*(?:toBe\s*\(\s*true\s*\)|toBeTruthy\s*\(\s*\))/;
7912
+ PAGE_VALUE_MATCHER = /^\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
7913
+ COUNT_ONE = /\.\s*toHaveCount\s*\(\s*1\s*\)/;
7914
+ URL_CHECK_ARG = /\bpage\s*\.\s*waitForURL\s*\(\s*([^,)]+)|\bexpect(?:\s*\.\s*soft)?\s*\(\s*page\s*\)\s*\.\s*toHaveURL\s*\(\s*([^,)]+)/;
7915
+ PLAIN_PAGE_PATH = /^\/[\w\-./~%]*(?:[?#][^\s]*)?$/;
7381
7916
  FORM_CONTROL_LOCATOR = /getByRole\s*\(\s*['"`](?:combobox|switch|checkbox|radio|textbox|searchbox|spinbutton|slider)['"`]|getByLabel\s*\(|getByPlaceholder\s*\(/;
7382
7917
  CONTROL_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(|\.toHaveAttribute\s*\(\s*['"`]aria-checked['"`]/;
7918
+ ACCOUNT_EMAIL_ENV = /^process\.env(?:(?:\?\.|\.)TEST_USER_EMAIL\b|(?:\?\.)?\[\s*['"]TEST_USER_EMAIL['"]\s*\])/;
7919
+ ENV_PASSWORD_ENTRY = /\.(?:fill|pressSequentially)\s*\(\s*process\.env(?:(?:\?\.|\.)TEST_USER_PASSWORD\b|(?:\?\.)?\[\s*['"]TEST_USER_PASSWORD['"]\s*\])/;
7920
+ ACCOUNT_CONTROL_ROLE = /getByRole\s*\(\s*['"`](?:button|link|menuitem)['"`]\s*,\s*\{/;
7921
+ ASSERT_OR_WAIT = /^(?:await\s+)?(?:expect(?:\.soft|\.poll)?\s*\(|[\w$]+(?:\.[\w$]+)*\.waitFor[A-Za-z]*\s*\()/;
7922
+ 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*\(/;
7923
+ actLinesOf = /* @__PURE__ */ new WeakMap();
7924
+ CLOSERS_ONLY = /^[})\];,\s]+$/;
7925
+ TEST_BODY_OPEN = /^(?:test|it)(?:\.(?:only|skip|fixme|slow))?\s*\(/;
7383
7926
  COPY_CONTROL = /\bcopy\b/i;
7384
7927
  COPIED_NOTICE = /\bcopied\b/i;
7385
7928
  SEND_CONTROL = /\b(send|submit|subscribe|sign up for|contact|feedback|join (the )?waitlist)\b/i;
7386
7929
  SEND_NOTICE = /\b(thank|thanks|sent|subscribed|received|in touch)\b/i;
7387
7930
  MARK_READ_CONTROL = /\bmark(?:ed)? as read\b|\bmark all(?: as)? read\b/i;
7388
7931
  APP_DATA_FIELD = /\b(title|name of|amount|price|quantity|sku|status|due|assignee|project|ticket|invoice|order)\b/i;
7932
+ COMMIT_CONTROL = /\b(?:save|submit|update|apply|add|create|post|send|publish)\b|\bconfirm\s+changes\b/i;
7933
+ 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;
7934
+ DESTRUCTIVE_CONTROL = /\b(?:delete|remove|discard)\b/i;
7935
+ ENTER_PRESS = /\.press\s*\(\s*['"`]Enter['"`]/;
7936
+ CONTROL_LABEL = /(?:name\s*:\s*|getBy(?:Text|Label|Title|TestId)\s*\(\s*|has-text\(\s*)(?:(['"`])((?:(?!\1).)*)\1|\/((?:\\.|[^/\\])+)\/)/g;
7937
+ isCommitClick = (stmt) => {
7938
+ const label = clickLabel(stmt);
7939
+ return label !== null && COMMIT_CONTROL.test(label) && !NOT_COMMIT_CONTROL.test(label) && !EXCLUDE_AFFORDANCE.test(label);
7940
+ };
7941
+ isDestructiveClick = (stmt) => {
7942
+ const label = clickLabel(stmt);
7943
+ return label !== null && DESTRUCTIVE_CONTROL.test(label);
7944
+ };
7945
+ TEARDOWN_OPEN = /\bfinally\s*\{|^(?:test\.)?(?:after|before)(?:Each|All)\s*\(/;
7389
7946
  }
7390
7947
  });
7391
7948
 
@@ -8733,177 +9290,143 @@ var init_admin_view_return_to = __esm({
8733
9290
  }
8734
9291
  });
8735
9292
 
8736
- // ../shared/dist/auth-plan.js
8737
- function hasInlineLoginSteps(playwrightCode) {
8738
- if (!playwrightCode)
8739
- return false;
8740
- return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
8741
- }
8742
- function hasGeneratedAuthGuard(playwrightCode) {
8743
- if (!playwrightCode)
8744
- return false;
8745
- return /\/\/ >>> VALIDATE_QA_AUTH v(\d+)\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\1\b/.test(playwrightCode);
9293
+ // ../shared/dist/auth-block-detector.js
9294
+ function stripEchoLines(text) {
9295
+ return text.split("\n").filter((line) => !ECHO_LINE_RE.test(line)).join("\n");
8746
9296
  }
8747
- function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
8748
- if (!sharedAuthInjectEnabled)
8749
- return false;
8750
- if (tags.includes("@auth"))
8751
- return false;
8752
- if (tags.includes("@api"))
8753
- return true;
8754
- if (!tags.includes("@needs-auth"))
8755
- return false;
8756
- if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
8757
- return true;
8758
- const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
8759
- const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
8760
- return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
9297
+ function matchesAny(text, patterns) {
9298
+ return patterns.some((p) => p.test(text));
8761
9299
  }
8762
- function codeReadsStoredCredentials(code) {
8763
- if (!code)
8764
- return false;
8765
- if (/__vqaLogin\b/.test(code))
8766
- return true;
8767
- for (const m of code.matchAll(/TEST_[A-Z0-9_]+/g)) {
8768
- if (/_(?:EMAIL|PASSWORD)/.test(m[0]))
8769
- return true;
8770
- }
8771
- for (const m of code.matchAll(/process\.env\s*\[([^\]]*)\]?/g)) {
8772
- if (!/^\s*(['"])[^'"`]*\1\s*$/.test(m[1]))
8773
- return true;
8774
- }
8775
- return false;
9300
+ function isIntentionalNegativeAuthTest(input) {
9301
+ const meta = [input.testName, input.description].filter(Boolean).join(" ");
9302
+ return matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH);
8776
9303
  }
8777
- function explicitAuthTypeTags(t) {
8778
- const found = /* @__PURE__ */ new Set();
8779
- for (const tag of t) {
8780
- if (AUTH_TYPE_TAGS.includes(tag))
8781
- found.add(tag);
8782
- else if (Object.hasOwn(AUTH_TAG_ALIASES, tag))
8783
- found.add(AUTH_TAG_ALIASES[tag]);
9304
+ function detectAuthBlock(input) {
9305
+ const tags = input.tags ?? [];
9306
+ const isAuthed = tags.some((t) => AUTHED_TAGS.includes(t));
9307
+ if (!isAuthed)
9308
+ return NO_DETECTION;
9309
+ if (isIntentionalNegativeAuthTest(input))
9310
+ return NO_DETECTION;
9311
+ const sources = [];
9312
+ if (input.error)
9313
+ sources.push(stripEchoLines(input.error));
9314
+ if (input.logs)
9315
+ sources.push(stripEchoLines(input.logs));
9316
+ for (const sr of input.stepResults ?? []) {
9317
+ if (sr.error)
9318
+ sources.push(stripEchoLines(sr.error));
8784
9319
  }
8785
- return [...found];
8786
- }
8787
- function invalidPlan(configurationError) {
8788
- return {
8789
- version: 1,
8790
- source: "invalid",
8791
- acquisition: "unsupported",
8792
- presentation: "none",
8793
- needsCookieState: false,
8794
- needsStandingHeaders: false,
8795
- standingHeadersMode: "none",
8796
- needsLoginCreds: false,
8797
- requiredEnvKeys: [],
8798
- configurationError
8799
- };
8800
- }
8801
- function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
8802
- return {
8803
- version: 1,
8804
- source: "explicit",
8805
- authTypeTag: authTypeTag2,
8806
- acquisition,
8807
- presentation,
8808
- needsCookieState: opts.cookie,
8809
- needsStandingHeaders: opts.standingMode === "required",
8810
- standingHeadersMode: opts.standingMode,
8811
- needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
8812
- requiredEnvKeys: opts.requiredEnvKeys
8813
- };
8814
- }
8815
- function deriveAuthPlan(tags, code, opts) {
8816
- const t = Array.isArray(tags) ? tags : [];
8817
- const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
8818
- const isApi = t.includes("@api");
8819
- const explicit = isApi ? explicitAuthTypeTags(t) : [];
8820
- if (explicit.length > 0) {
8821
- if (t.includes("@auth")) {
8822
- return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
8823
- }
8824
- const loginCompose = explicit.filter((e) => e === AUTH_TAG_COOKIE_STATE || e === AUTH_TAG_LOGIN_COOKIE || e === AUTH_TAG_LOGIN_BEARER);
8825
- if (explicit.length === 2 && explicit.includes(AUTH_TAG_STANDING) && loginCompose.length === 1) {
8826
- const composedKeys = [...TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY];
8827
- switch (loginCompose[0]) {
8828
- case AUTH_TAG_COOKIE_STATE:
8829
- return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "required", requiredEnvKeys: composedKeys });
8830
- case AUTH_TAG_LOGIN_COOKIE:
8831
- return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
8832
- case AUTH_TAG_LOGIN_BEARER:
8833
- return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
8834
- }
8835
- }
8836
- if (explicit.length > 1) {
8837
- return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
8838
- }
8839
- switch (explicit[0]) {
8840
- case AUTH_TAG_UNSUPPORTED:
9320
+ for (const text of sources) {
9321
+ if (!text)
9322
+ continue;
9323
+ for (const sig of AUTH_BLOCK_SIGNATURES) {
9324
+ if (sig.re.test(text)) {
8841
9325
  return {
8842
- ...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
9326
+ detected: true,
9327
+ reason: sig.reason,
9328
+ evidence: [`login-block signature "${sig.reason}" in: "${text.slice(0, 140).replace(/\s+/g, " ").trim()}"`]
8843
9329
  };
8844
- case AUTH_TAG_NONE:
8845
- return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
8846
- case AUTH_TAG_STANDING:
8847
- return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
8848
- case AUTH_TAG_LOGIN_BEARER:
8849
- return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
8850
- case AUTH_TAG_LOGIN_COOKIE:
8851
- return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
8852
- case AUTH_TAG_COOKIE_STATE:
8853
- return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
9330
+ }
8854
9331
  }
8855
9332
  }
8856
- const cookie = legacyNeedsCookieState(t, code, sharedFlag);
8857
- const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
8858
- const selfLoginWithoutStoredCreds = t.includes("@auth") && !t.includes("@needs-auth") && !isApi && !!code?.trim() && !/TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin\b/.test(code);
8859
- const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
8860
- const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
9333
+ return NO_DETECTION;
9334
+ }
9335
+ function detectApiColdSessionBlock(input) {
9336
+ const tags = input.tags ?? [];
9337
+ if (!tags.includes("@api"))
9338
+ return NO_DETECTION;
9339
+ const cookieMissing = input.authStateExpected && !input.authStateInjected;
9340
+ const standingMissing = !!input.needsStandingHeaders && !input.standingHeadersInjected;
9341
+ if (!cookieMissing && !standingMissing)
9342
+ return NO_DETECTION;
9343
+ const has401Status = (input.apiCalls ?? []).some((c) => c && (c.status === 401 || c.status === 403));
9344
+ const texts = [];
9345
+ if (input.error)
9346
+ texts.push(input.error);
9347
+ if (input.logs)
9348
+ texts.push(input.logs);
9349
+ for (const sr of input.stepResults ?? [])
9350
+ if (sr.error)
9351
+ texts.push(sr.error);
9352
+ const has401Text = texts.some((t) => UNAUTHORIZED_TEXT_RE.test(t));
9353
+ if (!has401Status && !has401Text)
9354
+ return NO_DETECTION;
9355
+ const missingAuth = cookieMissing ? "cold injected auth session (401/403 with no storageState)" : "missing standing auth header (401/403 with no API_AUTH_HEADERS installed)";
9356
+ const injectedNoun = cookieMissing ? "injected session was absent/expired" : "standing auth header was not installed";
8861
9357
  return {
8862
- version: 1,
8863
- source: "legacy",
8864
- acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
8865
- presentation: authedLegacy ? "cookie" : "none",
8866
- needsCookieState: cookie,
8867
- needsStandingHeaders: false,
8868
- // A legacy bare-@api test keeps injecting standing headers IF the project configured them
8869
- // (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
8870
- standingHeadersMode: isApi ? "legacy-if-configured" : "none",
8871
- needsLoginCreds,
8872
- requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
9358
+ detected: true,
9359
+ reason: missingAuth,
9360
+ evidence: [
9361
+ has401Status ? `a captured @api call returned 401/403 while the ${injectedNoun}` : `a 401/403 signal in the run error/logs while the ${injectedNoun}`
9362
+ ]
8873
9363
  };
8874
9364
  }
8875
- function authTypeTag(tags) {
8876
- const t = Array.isArray(tags) ? tags : [];
8877
- const explicit = explicitAuthTypeTags(t);
8878
- return explicit.length === 1 ? explicit[0] : void 0;
9365
+ function coldSessionConfigMessage(context, reason, evidence) {
9366
+ return `CONFIG_ISSUE: ${context} without valid injected auth (${reason}) \u2014 no session cookie was injected this run. Ensure the project login (auth-setup recipe / prewarm) is configured so the shared session jar warms (a project with no login: set TEST_USER_EMAIL and TEST_USER_PASSWORD in Test Data \u2192 Test Variables so a session can be captured); for endpoints that authenticate with a standing API key or bearer header instead, set them in the project's API_AUTH_HEADERS variable (injected as request headers). Evidence: ${evidence.join("; ")}`;
8879
9367
  }
8880
- 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;
8881
- var init_auth_plan = __esm({
8882
- "../shared/dist/auth-plan.js"() {
9368
+ var NO_DETECTION, AUTHED_TAGS, LOGIN_CTX, AUTH_BLOCK_SIGNATURES, INTENTIONAL_NEGATIVE_AUTH, ECHO_LINE_RE, UNAUTHORIZED_TEXT_RE;
9369
+ var init_auth_block_detector = __esm({
9370
+ "../shared/dist/auth-block-detector.js"() {
8883
9371
  "use strict";
8884
- AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
8885
- AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
8886
- AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
8887
- AUTH_TAG_STANDING = "@auth-standing";
8888
- AUTH_TAG_NONE = "@auth-none";
8889
- AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
8890
- AUTH_TYPE_TAGS = [
8891
- AUTH_TAG_COOKIE_STATE,
8892
- AUTH_TAG_LOGIN_COOKIE,
8893
- AUTH_TAG_LOGIN_BEARER,
8894
- AUTH_TAG_STANDING,
8895
- AUTH_TAG_NONE,
8896
- AUTH_TAG_UNSUPPORTED
9372
+ NO_DETECTION = { detected: false, reason: "", evidence: [] };
9373
+ AUTHED_TAGS = ["@needs-auth", "@inline-login"];
9374
+ LOGIN_CTX = "\\b(?:log\\s*in|login|logging\\s+in|sign[\\s-]?in|signing\\s+in|continue)\\b";
9375
+ AUTH_BLOCK_SIGNATURES = [
9376
+ { reason: "incorrect password", re: /\b(incorrect|invalid|wrong|bad)\s+password\b/i },
9377
+ { reason: "password is incorrect", re: /\bpassword\s+(is\s+)?(incorrect|invalid|wrong)\b/i },
9378
+ { reason: "invalid credentials", re: /\b(invalid|incorrect|wrong|bad)\s+(login\s+)?credentials\b/i },
9379
+ { reason: "credentials do not match", re: /\bcredentials\b[^\n]{0,40}\b(do(?:es)?\s*n['’]?t|do\s+not)\s+match\b/i },
9380
+ { reason: "incorrect email or password", re: /\b(incorrect|invalid|wrong)\s+(email|username)\s+or\s+password\b/i },
9381
+ // Require an EXPLICIT negative (codex #21): the old `(not\s+)?(confirmed|verified)` made the negation
9382
+ // optional, so a benign "Email verified" success message / feature assertion matched and hid a real
9383
+ // feature failure as a CONFIG_ISSUE. Only "email not (yet) confirmed/verified" or "unconfirmed/unverified".
9384
+ { reason: "email not confirmed", re: /\b(e-?mail)\b[^\n]{0,30}\b(not\s+(?:yet\s+)?(?:confirmed|verified)|unconfirmed|unverified)\b/i },
9385
+ { reason: "please confirm your email", re: /\bplease\s+(confirm|verify)\s+your\s+(e-?mail)\b/i },
9386
+ // The bare "confirm/verify your email" and "account not found" phrases are ALSO ordinary feature copy
9387
+ // ("Verify your email banner", a 404-style "Account not found" page), so they only count with a
9388
+ // login/sign-in context on the SAME line (either side). "Please confirm/verify your email" above
9389
+ // is already an explicit prompt and stays unconditional.
9390
+ { reason: "confirm your email to continue", re: new RegExp(`${LOGIN_CTX}[^\\n]{0,80}\\bconfirm\\s+your\\s+e-?mail\\b|\\bconfirm\\s+your\\s+e-?mail\\b[^\\n]{0,80}${LOGIN_CTX}`, "i") },
9391
+ { reason: "verify your email to continue", re: new RegExp(`${LOGIN_CTX}[^\\n]{0,80}\\bverify\\s+your\\s+e-?mail\\b|\\bverify\\s+your\\s+e-?mail\\b[^\\n]{0,80}${LOGIN_CTX}`, "i") },
9392
+ {
9393
+ reason: "account not found",
9394
+ re: new RegExp(`${LOGIN_CTX}[^\\n]{0,80}\\baccount\\b[^\\n]{0,20}\\b(?:not\\s+found|does(?:n['\u2019]t| not)\\s+exist)\\b|\\baccount\\b[^\\n]{0,20}\\b(?:not\\s+found|does(?:n['\u2019]t| not)\\s+exist)\\b[^\\n]{0,80}${LOGIN_CTX}`, "i")
9395
+ },
9396
+ { reason: "authentication failed", re: /\bauthentication\s+failed\b/i },
9397
+ { reason: "login failed", re: /\b(login|log\s*in|sign[\s-]?in)\s+failed\b/i }
8897
9398
  ];
8898
- AUTH_TAG_ALIASES = {
8899
- "@auth-cookie": AUTH_TAG_COOKIE_STATE,
8900
- "@auth-bearer": AUTH_TAG_LOGIN_BEARER,
8901
- "@auth-token": AUTH_TAG_LOGIN_BEARER,
8902
- "@auth-api-key": AUTH_TAG_STANDING,
8903
- "@auth-standing-header": AUTH_TAG_STANDING
8904
- };
8905
- TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
8906
- API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
9399
+ INTENTIONAL_NEGATIVE_AUTH = [
9400
+ /\b(invalid|incorrect|wrong|bad)\s+(password|credentials|email|login)\b/i,
9401
+ /\b(negative|unhappy)\s+(login|auth|sign[\s-]?in|path|case|flow|test)\b/i,
9402
+ // "should fail" et al must be TIED to an auth noun (codex #21): a bare `should\s+(fail|reject|deny)`
9403
+ // exempted benign names like "Export should fail gracefully", silently disabling the anti-lockout
9404
+ // protection for them. Require a login/auth noun on either side of the "should fail / fail to log in".
9405
+ /\b(login|log\s*in|sign[\s-]?in|auth\w*|credential|password)\b[^\n]{0,40}\bshould\s+(fail|reject|deny)\b/i,
9406
+ /\bshould\s+(fail|reject|deny|not\s+log(?:\s*in)?)\b[^\n]{0,40}\b(login|log\s*in|sign[\s-]?in|auth\w*|credential|password|unauthenticated)\b/i,
9407
+ /\bfailed?\s+login\b/i,
9408
+ /\blogin\s+(error|failure|rejected?)\b/i,
9409
+ /\b(reject|deny|denied|block(ed)?)\b[^\n]{0,20}\b(login|sign[\s-]?in|auth|credential|password)\b/i
9410
+ ];
9411
+ ECHO_LINE_RE = /^\s*(?:(?:📋|🎭|🌐|⚙️?)|(?:stderr:\s*|stdout:\s*)?Locator:)/u;
9412
+ 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;
9413
+ }
9414
+ });
9415
+
9416
+ // ../shared/dist/auth-session-stamp.js
9417
+ function parseAuthSessionStamp(value) {
9418
+ if (!value || typeof value !== "object" || Array.isArray(value))
9419
+ return null;
9420
+ const { shared, role, generation } = value;
9421
+ if (shared !== true || role !== "producer" && role !== "follower")
9422
+ return null;
9423
+ return typeof generation === "string" && AUTH_SESSION_GENERATION_RE.test(generation) ? { shared: true, role, generation } : { shared: true, role };
9424
+ }
9425
+ var AUTH_SESSION_GENERATION_RE;
9426
+ var init_auth_session_stamp = __esm({
9427
+ "../shared/dist/auth-session-stamp.js"() {
9428
+ "use strict";
9429
+ AUTH_SESSION_GENERATION_RE = /^[0-9a-z]{1,16}$/;
8907
9430
  }
8908
9431
  });
8909
9432
 
@@ -9084,11 +9607,48 @@ function storageStateExpiredBySkew(stateJson, nowMs, skewMs = AUTH_STATE_EXPIRY_
9084
9607
  const liveSession = tokens2.some((t) => t.expiryMs > deadline && t.sessionClass);
9085
9608
  return !liveSession;
9086
9609
  }
9087
- var AUTH_STATE_EXPIRY_SKEW_MS;
9088
- var init_auth_state_freshness = __esm({
9089
- "../shared/dist/auth-state-freshness.js"() {
9610
+ var AUTH_STATE_EXPIRY_SKEW_MS;
9611
+ var init_auth_state_freshness = __esm({
9612
+ "../shared/dist/auth-state-freshness.js"() {
9613
+ "use strict";
9614
+ AUTH_STATE_EXPIRY_SKEW_MS = 3e4;
9615
+ }
9616
+ });
9617
+
9618
+ // ../shared/dist/skip-coverage-detector.js
9619
+ function detectSkippedApiCoverage(steps) {
9620
+ const allSkipped = steps.length > 0 && steps.every((s) => s.status === "skipped");
9621
+ if (allSkipped) {
9622
+ if (steps.every((s) => s.skipKind === "fixme")) {
9623
+ return {
9624
+ detected: false,
9625
+ logLine: "All @api tests skipped, but EVERY skip is a deliberate fixme (intentional N/A \u2014 e.g. a UI-only redirect login) \u2014 NOT relabelling CONFIG_ISSUE"
9626
+ };
9627
+ }
9628
+ return {
9629
+ detected: true,
9630
+ error: (
9631
+ // Name BOTH auth mechanisms (item 5d): the all-skipped copy used to say only "refresh the auth
9632
+ // state", which misdirects a @auth-standing operator (whose real gap is a missing API_AUTH_HEADERS
9633
+ // key, not a cold cookie session) to warm a login that was never the problem.
9634
+ "CONFIG_ISSUE: Authenticated @api coverage was NOT exercised \u2014 every test skipped (cold injected session, a missing standing auth header, or an unresolved fixme). Provide the auth the endpoints need \u2014 warm the project login/session cookie (auth-setup recipe / prewarm), or set a standing API key/bearer in the project's API_AUTH_HEADERS variable \u2014 so the assertions actually run."
9635
+ ),
9636
+ logLine: "All @api tests skipped \u2014 posting ERROR/CONFIG_ISSUE (not PASSED) so zero authenticated coverage is visible, not silent green"
9637
+ };
9638
+ }
9639
+ const runtimeSkipped = steps.filter((s) => s.status === "skipped" && s.skipKind === "skip");
9640
+ if (runtimeSkipped.length === 0)
9641
+ return { detected: false };
9642
+ const reasons = [...new Set(runtimeSkipped.map((s) => `"${s.title ?? "test"}"${s.skipReason ? ` (${s.skipReason})` : ""}`))].join("; ");
9643
+ return {
9644
+ detected: true,
9645
+ error: `CONFIG_ISSUE: ${runtimeSkipped.length} of ${steps.length} @api tests skipped at runtime \u2014 that coverage was NOT exercised. Skipped: ${reasons}`,
9646
+ logLine: `${runtimeSkipped.length}/${steps.length} @api tests skipped at runtime \u2014 posting ERROR/CONFIG_ISSUE (not PASSED) so the coverage hole is visible, not silent green`
9647
+ };
9648
+ }
9649
+ var init_skip_coverage_detector = __esm({
9650
+ "../shared/dist/skip-coverage-detector.js"() {
9090
9651
  "use strict";
9091
- AUTH_STATE_EXPIRY_SKEW_MS = 3e4;
9092
9652
  }
9093
9653
  });
9094
9654
 
@@ -9783,6 +10343,77 @@ var init_deepgram_speech = __esm({
9783
10343
  }
9784
10344
  });
9785
10345
 
10346
+ // ../shared/dist/feature-flags.js
10347
+ function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
10348
+ return value?.trim().toLowerCase() !== "false";
10349
+ }
10350
+ function isAuthReadinessGuardEnabled(value = process.env.AUTH_READINESS_GUARD) {
10351
+ return value?.trim().toLowerCase() !== "false";
10352
+ }
10353
+ function visualBaselinesEnabled(projectId, raw = process.env.VISUAL_BASELINES_PROJECTS) {
10354
+ if (typeof projectId !== "string" || projectId.trim() === "")
10355
+ return false;
10356
+ const listed = (raw ?? "").split(",").map((entry) => entry.trim()).filter(Boolean);
10357
+ return listed.includes("*") || listed.includes(projectId);
10358
+ }
10359
+ var init_feature_flags = __esm({
10360
+ "../shared/dist/feature-flags.js"() {
10361
+ "use strict";
10362
+ }
10363
+ });
10364
+
10365
+ // ../shared/dist/visual-compare-limits.js
10366
+ function isSafeMaskSelector(selector) {
10367
+ if (typeof selector !== "string")
10368
+ return false;
10369
+ if (selector.trim().length === 0)
10370
+ return false;
10371
+ if (selector.length > VISUAL_MASK_SELECTOR_MAX_LENGTH)
10372
+ return false;
10373
+ return !VISUAL_MASK_SELECTOR_FORBIDDEN.test(selector);
10374
+ }
10375
+ var VISUAL_MASK_SELECTORS_MAX, VISUAL_MASK_SELECTOR_MAX_LENGTH, VISUAL_MASK_SELECTOR_FORBIDDEN;
10376
+ var init_visual_compare_limits = __esm({
10377
+ "../shared/dist/visual-compare-limits.js"() {
10378
+ "use strict";
10379
+ VISUAL_MASK_SELECTORS_MAX = 50;
10380
+ VISUAL_MASK_SELECTOR_MAX_LENGTH = 300;
10381
+ VISUAL_MASK_SELECTOR_FORBIDDEN = /[{};@<\r\n]/;
10382
+ }
10383
+ });
10384
+
10385
+ // ../shared/dist/visual-baseline-identity.js
10386
+ function isBrowserVersion(value) {
10387
+ return typeof value === "string" && value.length <= 32 && BROWSER_VERSION.test(value);
10388
+ }
10389
+ function renderIdentity(flavour, version, image) {
10390
+ if (!/^[a-z][a-z-]{0,31}$/.test(flavour) || !/^[a-z][a-z-]{0,31}$/.test(image) || !isBrowserVersion(version))
10391
+ return null;
10392
+ const identity = `${flavour}/${version}/${image}`;
10393
+ return identity.length <= RENDER_IDENTITY_MAX_LENGTH ? identity : null;
10394
+ }
10395
+ function v1RenderIdentity(version) {
10396
+ return renderIdentity(VISUAL_V1_IDENTITY.flavour, version, VISUAL_V1_IDENTITY.image);
10397
+ }
10398
+ function parseRenderIdentity(value) {
10399
+ if (typeof value !== "string" || value.length > RENDER_IDENTITY_MAX_LENGTH)
10400
+ return null;
10401
+ const parts = value.split("/");
10402
+ if (parts.length !== 3)
10403
+ return null;
10404
+ const [flavour, version, image] = parts;
10405
+ return renderIdentity(flavour, version, image) === value ? { flavour, version, image } : null;
10406
+ }
10407
+ var VISUAL_V1_IDENTITY, RENDER_IDENTITY_MAX_LENGTH, BROWSER_VERSION;
10408
+ var init_visual_baseline_identity = __esm({
10409
+ "../shared/dist/visual-baseline-identity.js"() {
10410
+ "use strict";
10411
+ VISUAL_V1_IDENTITY = { platform: "linux", image: "runner-fast", flavour: "headless-shell" };
10412
+ RENDER_IDENTITY_MAX_LENGTH = 64;
10413
+ BROWSER_VERSION = /^\d{1,4}(\.\d{1,6}){0,3}$/;
10414
+ }
10415
+ });
10416
+
9786
10417
  // ../shared/dist/analytics-url.js
9787
10418
  function isSensitiveQueryKey(key) {
9788
10419
  return SENSITIVE_QUERY_KEY_RE.test(key);
@@ -10085,7 +10716,7 @@ var init_agentic_cli_runtime = __esm({
10085
10716
  GROK_CLI_DEFAULT_MODEL = "grok-4.7";
10086
10717
  GROK_CLI_DEFAULT_REASONING_EFFORT = "high";
10087
10718
  CLAUDE_CLI_DEFAULT_MODEL = "claude-sonnet-5-5";
10088
- CLAUDE_CLI_DEFAULT_EFFORT = "max";
10719
+ CLAUDE_CLI_DEFAULT_EFFORT = "high";
10089
10720
  MUSE_CLI_DEFAULT_MODEL = "muse-spark-1.3-contributor";
10090
10721
  MUSE_CLI_DEFAULT_EFFORT = "high";
10091
10722
  CODEX_CLI_DEFAULT_MODEL = "gpt-6.1-sol";
@@ -10140,6 +10771,17 @@ function openCodeGoWireModel(id) {
10140
10771
  const trimmed = id?.trim() ?? "";
10141
10772
  return isOpenCodeGoModelId(trimmed) ? trimmed.slice(OPENCODE_GO_PREFIX.length) : null;
10142
10773
  }
10774
+ function isZaiModelId(id) {
10775
+ const trimmed = id?.trim() ?? "";
10776
+ return ZAI_MODEL_PATTERN.test(trimmed) && !trimmed.includes("..");
10777
+ }
10778
+ function hasZaiPrefix(id) {
10779
+ return (id?.trim() ?? "").startsWith(ZAI_PREFIX);
10780
+ }
10781
+ function zaiWireModel(id) {
10782
+ const trimmed = id?.trim() ?? "";
10783
+ return isZaiModelId(trimmed) ? trimmed.slice(ZAI_PREFIX.length) : null;
10784
+ }
10143
10785
  function isEngineId(value) {
10144
10786
  return CLI_ENGINE_MODEL_IDS.includes(value);
10145
10787
  }
@@ -10236,7 +10878,7 @@ function museCliContributorMode(env = process.env) {
10236
10878
  const raw = (env.MUSE_CLI_CONTRIBUTOR_MODE ?? "").trim().toLowerCase();
10237
10879
  return raw === "off" || raw === "false" || raw === "0" || raw === "no" ? "off" : "on";
10238
10880
  }
10239
- 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;
10881
+ 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;
10240
10882
  var init_engine_spec = __esm({
10241
10883
  "../shared/dist/engine-spec.js"() {
10242
10884
  "use strict";
@@ -10296,6 +10938,8 @@ var init_engine_spec = __esm({
10296
10938
  OPENROUTER_MODEL_PATTERN = /^openrouter:[a-z0-9][a-z0-9._-]{0,40}\/[A-Za-z0-9._:-]{1,100}$/;
10297
10939
  OPENCODE_GO_PREFIX = "opencode-go:";
10298
10940
  OPENCODE_GO_MODEL_PATTERN = /^opencode-go:[a-z0-9][a-z0-9._-]{0,100}$/;
10941
+ ZAI_PREFIX = "zai:";
10942
+ ZAI_MODEL_PATTERN = /^zai:glm-[a-z0-9][a-z0-9._-]{0,60}$/;
10299
10943
  }
10300
10944
  });
10301
10945
 
@@ -10580,6 +11224,8 @@ var init_api_model_catalog = __esm({
10580
11224
  authCaptureModel: "Login capture",
10581
11225
  plannerModel: "Scenario Planner",
10582
11226
  plannerGateModel: "Planner Gate",
11227
+ pwAgentsHealerModel: "Test Healer (Playwright agents)",
11228
+ pwAgentsReviewModel: "Plan Review (Playwright agents)",
10583
11229
  generationModel: "Code Generation",
10584
11230
  reviewModel: "Code Review",
10585
11231
  apiTestModel: "API Test Generator",
@@ -10604,11 +11250,6 @@ var init_api_model_catalog = __esm({
10604
11250
  });
10605
11251
 
10606
11252
  // ../shared/dist/provider-chains.js
10607
- function isClaudeModel(id) {
10608
- if (/claude|anthropic/i.test(id))
10609
- return true;
10610
- return API_MODEL_CATALOG.some((m) => m.provider === "anthropic" && (m.id === id || m.apiModel === id));
10611
- }
10612
11253
  function chainKey(modelId) {
10613
11254
  const id = canonicalModelId(modelId);
10614
11255
  if (!id)
@@ -10616,35 +11257,334 @@ function chainKey(modelId) {
10616
11257
  const entry = API_MODEL_CATALOG.find((m) => m.id === id || m.apiModel === id);
10617
11258
  return entry?.id ?? id;
10618
11259
  }
11260
+ function has(map, key) {
11261
+ return Object.prototype.hasOwnProperty.call(map, key);
11262
+ }
10619
11263
  function providerChainFor(modelId) {
10620
11264
  const key = chainKey(modelId);
10621
- if (!key || isClaudeModel(key))
10622
- return [];
10623
- return Object.prototype.hasOwnProperty.call(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
11265
+ if (key && has(_overrideHops, key))
11266
+ return _overrideHops[key];
11267
+ return key && has(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
11268
+ }
11269
+ function providerChainLeadsFor(modelId) {
11270
+ const key = chainKey(modelId);
11271
+ if (key && has(_overrideLeads, key))
11272
+ return _overrideLeads[key];
11273
+ return key && has(PROVIDER_CHAIN_LEADS, key) ? PROVIDER_CHAIN_LEADS[key] : [];
11274
+ }
11275
+ function allChains() {
11276
+ const ordered = (map, override) => [...Object.entries(map).filter(([key]) => !has(override, key)), ...Object.entries(override)];
11277
+ return [...ordered(PROVIDER_CHAINS, _overrideHops), ...ordered(PROVIDER_CHAIN_LEADS, _overrideLeads)];
10624
11278
  }
10625
11279
  function isProviderChainHopId(modelId) {
10626
11280
  const id = modelId ?? "";
10627
- return id !== "" && Object.values(PROVIDER_CHAINS).some((hops) => hops.includes(id));
11281
+ return id !== "" && allChains().some(([, hops]) => hops.includes(id));
10628
11282
  }
10629
11283
  function providerChainPrimariesForHop(hopId) {
10630
11284
  const id = hopId ?? "";
10631
11285
  if (!id)
10632
11286
  return [];
10633
- return Object.entries(PROVIDER_CHAINS).filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
11287
+ return allChains().filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
11288
+ }
11289
+ function providerChainHostOf(value, modelValue) {
11290
+ if (value === modelValue)
11291
+ return "own";
11292
+ if (hasZaiPrefix(value))
11293
+ return "zai";
11294
+ if (hasOpenCodeGoPrefix(value))
11295
+ return "opencode-go";
11296
+ if (hasOpenRouterPrefix(value))
11297
+ return "openrouter";
11298
+ return "own";
11299
+ }
11300
+ function providerChainHostProvider(value) {
11301
+ if (typeof value !== "string" || !value)
11302
+ return null;
11303
+ if (hasOpenRouterPrefix(value))
11304
+ return "openrouter";
11305
+ if (hasOpenCodeGoPrefix(value))
11306
+ return "opencode-go";
11307
+ if (hasZaiPrefix(value))
11308
+ return "zai";
11309
+ const key = chainKey(value);
11310
+ return API_MODEL_CATALOG.find((m) => m.id === key)?.provider ?? GEMINI_ASSISTANT_MODELS.find((m) => m.id === key)?.provider ?? null;
11311
+ }
11312
+ function providerHostPlan(modelId) {
11313
+ const key = chainKey(modelId);
11314
+ if (!key)
11315
+ return [];
11316
+ const own = { host: hasOpenRouterPrefix(key) ? "openrouter" : "own", value: key };
11317
+ const steps = [
11318
+ ...providerChainLeadsFor(key).map((value) => ({ host: providerChainHostOf(value, key), value })),
11319
+ own,
11320
+ ...providerChainFor(key).map((value) => ({ host: providerChainHostOf(value, key), value }))
11321
+ ];
11322
+ const notOffered = Object.prototype.hasOwnProperty.call(PROVIDER_HOST_NOT_OFFERED, key) ? PROVIDER_HOST_NOT_OFFERED[key] : {};
11323
+ for (const [host, reason] of Object.entries(notOffered)) {
11324
+ const at = steps.findIndex((step) => step === own) + 1;
11325
+ steps.splice(at, 0, { host, value: null, notOffered: reason });
11326
+ }
11327
+ return steps;
11328
+ }
11329
+ function providerChainModelKey(modelId) {
11330
+ return chainKey(modelId);
11331
+ }
11332
+ function hasProviderChainLeadsByDefault(modelId) {
11333
+ const key = chainKey(modelId);
11334
+ return key !== null && has(PROVIDER_CHAIN_LEADS, key);
11335
+ }
11336
+ function isChainablePrimary(id) {
11337
+ return typeof id === "string" && id !== "" && (has(PROVIDER_CHAINS, id) || has(PROVIDER_CHAIN_LEADS, id));
11338
+ }
11339
+ function defaultProviderChainEntries(primary) {
11340
+ if (!isChainablePrimary(primary))
11341
+ return [];
11342
+ const leads = has(PROVIDER_CHAIN_LEADS, primary) ? PROVIDER_CHAIN_LEADS[primary] : [];
11343
+ const hops = has(PROVIDER_CHAINS, primary) ? PROVIDER_CHAINS[primary] : [];
11344
+ return [...leads, primary, ...hops].map((hop) => ({ hop, enabled: true }));
11345
+ }
11346
+ function allowedProviderChainHosts(primary) {
11347
+ return defaultProviderChainEntries(primary).map((entry) => entry.hop);
11348
+ }
11349
+ function isPlainObject2(value) {
11350
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
11351
+ }
11352
+ function validateProviderChainEntries(primary, entries) {
11353
+ if (!isChainablePrimary(primary))
11354
+ return `${String(primary).slice(0, 80)} has no provider chain to order`;
11355
+ if (!Array.isArray(entries))
11356
+ return `${primary}: the chain must be a list`;
11357
+ if (entries.length > PROVIDER_CHAIN_MAX_ENTRIES)
11358
+ return `${primary}: at most ${PROVIDER_CHAIN_MAX_ENTRIES} hosts`;
11359
+ const allowed = allowedProviderChainHosts(primary);
11360
+ const seen = /* @__PURE__ */ new Set();
11361
+ for (const entry of entries) {
11362
+ if (!isPlainObject2(entry))
11363
+ return `${primary}: each host must be { hop, enabled }`;
11364
+ const keys = Object.keys(entry);
11365
+ if (keys.length !== 2 || !keys.includes("hop") || !keys.includes("enabled"))
11366
+ return `${primary}: each host must be { hop, enabled } and nothing else`;
11367
+ const { hop, enabled } = entry;
11368
+ if (typeof enabled !== "boolean")
11369
+ return `${primary}: enabled must be true or false`;
11370
+ if (typeof hop !== "string" || !hop)
11371
+ return `${primary}: hop must be a model value`;
11372
+ if (!allowed.includes(hop))
11373
+ return `${primary}: ${hop.slice(0, 80)} is not a host of this model`;
11374
+ if (seen.has(hop))
11375
+ return `${primary}: ${hop} is listed twice`;
11376
+ if (hop === primary && !enabled)
11377
+ return `${primary}: the model's own value stays on (pick another model on AI Models instead)`;
11378
+ seen.add(hop);
11379
+ }
11380
+ if (!seen.has(primary))
11381
+ return `${primary}: list the model's own value once, where its own host sits`;
11382
+ return null;
11383
+ }
11384
+ function decodeProviderChains(raw) {
11385
+ let value = raw;
11386
+ if (typeof raw === "string") {
11387
+ if (raw.length > PROVIDER_CHAINS_SETTING_MAX_CHARS)
11388
+ return { ok: false, error: `AI_PROVIDER_CHAINS is too long (at most ${PROVIDER_CHAINS_SETTING_MAX_CHARS} characters)` };
11389
+ try {
11390
+ value = JSON.parse(raw);
11391
+ } catch {
11392
+ return { ok: false, error: "AI_PROVIDER_CHAINS must be valid JSON" };
11393
+ }
11394
+ } else if (JSON.stringify(raw ?? null).length > PROVIDER_CHAINS_SETTING_MAX_CHARS) {
11395
+ return { ok: false, error: `AI_PROVIDER_CHAINS is too long (at most ${PROVIDER_CHAINS_SETTING_MAX_CHARS} characters)` };
11396
+ }
11397
+ if (!isPlainObject2(value))
11398
+ return { ok: false, error: 'AI_PROVIDER_CHAINS must be an object: { "<model>": [ { "hop": "<model value>", "enabled": true } ] }' };
11399
+ if (Object.keys(value).length > PROVIDER_CHAIN_MAX_PRIMARIES)
11400
+ return { ok: false, error: `AI_PROVIDER_CHAINS names at most ${PROVIDER_CHAIN_MAX_PRIMARIES} models` };
11401
+ return { ok: true, value };
11402
+ }
11403
+ function checkedProviderChains(value) {
11404
+ const out = {};
11405
+ for (const [primary, entries] of Object.entries(value)) {
11406
+ const problem = validateProviderChainEntries(primary, entries);
11407
+ if (problem)
11408
+ return { ok: false, error: problem };
11409
+ out[primary] = entries.map(({ hop, enabled }) => ({ hop, enabled }));
11410
+ }
11411
+ return { ok: true, value: out };
11412
+ }
11413
+ function parseProviderChainOverrides(raw) {
11414
+ const decoded = decodeProviderChains(raw);
11415
+ return decoded.ok ? checkedProviderChains(decoded.value) : decoded;
11416
+ }
11417
+ function parseStoredProviderChainOverrides(raw) {
11418
+ const decoded = decodeProviderChains(raw);
11419
+ if (!decoded.ok)
11420
+ return decoded;
11421
+ const dropped = [];
11422
+ const kept = {};
11423
+ for (const [primary, entries] of Object.entries(decoded.value)) {
11424
+ if (!isChainablePrimary(primary) || !Array.isArray(entries)) {
11425
+ kept[primary] = entries;
11426
+ continue;
11427
+ }
11428
+ const allowed = allowedProviderChainHosts(primary);
11429
+ kept[primary] = entries.filter((entry) => {
11430
+ const hop = isPlainObject2(entry) ? entry.hop : void 0;
11431
+ if (typeof hop !== "string" || hop === "" || allowed.includes(hop))
11432
+ return true;
11433
+ dropped.push({ primary, hop: hop.slice(0, 80) });
11434
+ return false;
11435
+ });
11436
+ }
11437
+ const checked = checkedProviderChains(kept);
11438
+ return checked.ok ? { ...checked, dropped } : checked;
11439
+ }
11440
+ function validateProviderChainsSetting(raw) {
11441
+ const parsed = parseProviderChainOverrides(raw);
11442
+ return parsed.ok ? null : parsed.error;
10634
11443
  }
10635
- var PROVIDER_CHAINS;
11444
+ function setProviderChainOverrides(value, onDropped) {
11445
+ const parsed = value === null || value === void 0 || value === "" ? null : parseStoredProviderChainOverrides(value);
11446
+ if (parsed?.ok && parsed.dropped.length > 0)
11447
+ onDropped?.(parsed.dropped);
11448
+ _overrides = parsed?.ok ? parsed.value : null;
11449
+ _overrideLeads = {};
11450
+ _overrideHops = {};
11451
+ for (const [primary, entries] of Object.entries(_overrides ?? {})) {
11452
+ const at = entries.findIndex((entry) => entry.hop === primary);
11453
+ const on = (list) => list.filter((entry) => entry.enabled).map((entry) => entry.hop);
11454
+ _overrideLeads[primary] = on(entries.slice(0, at));
11455
+ _overrideHops[primary] = on(entries.slice(at + 1));
11456
+ }
11457
+ return parsed === null || parsed.ok;
11458
+ }
11459
+ function getProviderChainOverrides() {
11460
+ return _overrides;
11461
+ }
11462
+ 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;
10636
11463
  var init_provider_chains = __esm({
10637
11464
  "../shared/dist/provider-chains.js"() {
10638
11465
  "use strict";
10639
11466
  init_api_model_catalog();
11467
+ init_engine_spec();
10640
11468
  PROVIDER_CHAINS = {
10641
- "minimax-m3": ["openrouter:minimax/minimax-m3", "opencode-go:minimax-m3"],
10642
- "minimax-m3.1-flash": ["openrouter:minimax/minimax-m3.1-flash", "opencode-go:minimax-m3.1-flash"],
11469
+ "minimax-m3": ["opencode-go:minimax-m3", "openrouter:minimax/minimax-m3"],
11470
+ "minimax-m3.1-flash": ["opencode-go:minimax-m3.1-flash", "openrouter:minimax/minimax-m3.1-flash"],
10643
11471
  "muse-spark-1.3-contributor": ["openrouter:meta/muse-spark-1.3-contributor"],
10644
- "openrouter:z-ai/glm-5.3-flash": ["opencode-go:glm-5.3-flash"],
10645
- "openrouter:z-ai/glm-5.3": ["opencode-go:glm-5.3"],
10646
- "openrouter:z-ai/glm-5.2": ["opencode-go:glm-5.2"]
11472
+ "claude-haiku-5-5": ["opencode-go:claude-haiku-5-5", "openrouter:anthropic/claude-haiku-5.5"],
11473
+ "gemini-3.1-pro-preview": ["openrouter:google/gemini-3.1-pro-preview"],
11474
+ "deepseek-flash": ["opencode-go:deepseek-v4.1-flash", "openrouter:deepseek/deepseek-v4.1-flash"],
11475
+ "deepseek-v4-pro": ["opencode-go:deepseek-v4-pro", "openrouter:deepseek/deepseek-v4-pro"]
10647
11476
  };
11477
+ PROVIDER_CHAIN_LEADS = {
11478
+ "openrouter:z-ai/glm-5.3-flash": ["zai:glm-5.3-flash", "opencode-go:glm-5.3-flash"],
11479
+ "openrouter:z-ai/glm-5.3": ["zai:glm-5.3", "opencode-go:glm-5.3"],
11480
+ "openrouter:z-ai/glm-5.2": ["zai:glm-5.2", "opencode-go:glm-5.2"]
11481
+ };
11482
+ PROVIDER_HOST_NOT_OFFERED = {
11483
+ "muse-spark-1.3-contributor": { "opencode-go": "OpenCode Go serves Muse only on its Responses API" },
11484
+ "gemini-3.1-pro-preview": { "opencode-go": "OpenCode Go does not serve Gemini" }
11485
+ };
11486
+ PROVIDER_CHAIN_MAX_ENTRIES = 8;
11487
+ PROVIDER_CHAINS_SETTING_MAX_CHARS = 16e3;
11488
+ PROVIDER_CHAIN_MAX_PRIMARIES = 32;
11489
+ ALLOWED_PROVIDER_CHAIN_HOPS = /* @__PURE__ */ new Set([
11490
+ ...Object.values(PROVIDER_CHAINS).flat(),
11491
+ ...Object.values(PROVIDER_CHAIN_LEADS).flat()
11492
+ ]);
11493
+ _overrides = null;
11494
+ _overrideLeads = {};
11495
+ _overrideHops = {};
11496
+ }
11497
+ });
11498
+
11499
+ // ../shared/dist/provider-accounts.js
11500
+ function modelNames(modelId) {
11501
+ const value = modelId.trim();
11502
+ const names = /* @__PURE__ */ new Set([value]);
11503
+ for (const prefix of HOST_PREFIXES) {
11504
+ if (!value.startsWith(prefix))
11505
+ continue;
11506
+ const bare = value.slice(prefix.length);
11507
+ names.add(bare);
11508
+ const slash = bare.lastIndexOf("/");
11509
+ if (slash >= 0)
11510
+ names.add(bare.slice(slash + 1));
11511
+ }
11512
+ const canonical = canonicalModelId(value);
11513
+ if (canonical) {
11514
+ names.add(canonical);
11515
+ const entry = API_MODEL_CATALOG.find((m) => m.id === canonical || m.apiModel === canonical);
11516
+ if (entry) {
11517
+ names.add(entry.id);
11518
+ if (entry.apiModel)
11519
+ names.add(entry.apiModel);
11520
+ }
11521
+ }
11522
+ return names;
11523
+ }
11524
+ function accountServesModel(models, modelId) {
11525
+ if (!models || models.length === 0)
11526
+ return true;
11527
+ if (!modelId)
11528
+ return false;
11529
+ const names = modelNames(modelId);
11530
+ return models.some((model) => names.has(model.trim()));
11531
+ }
11532
+ function normalizeProviderAccountModels(models) {
11533
+ if (!Array.isArray(models))
11534
+ return null;
11535
+ const out = [];
11536
+ for (const model of models) {
11537
+ if (typeof model !== "string")
11538
+ return null;
11539
+ const trimmed = model.trim();
11540
+ if (!PROVIDER_ACCOUNT_MODEL_PATTERN.test(trimmed) || trimmed.includes(".."))
11541
+ return null;
11542
+ if (!out.includes(trimmed))
11543
+ out.push(trimmed);
11544
+ }
11545
+ return out.length > MAX_PROVIDER_ACCOUNT_MODELS ? null : out;
11546
+ }
11547
+ function parseProviderAccountLists(value, providers) {
11548
+ if (!value || typeof value !== "object" || Array.isArray(value))
11549
+ return null;
11550
+ const parsed = {};
11551
+ for (const provider of providers) {
11552
+ const list = value[provider];
11553
+ if (!Array.isArray(list))
11554
+ continue;
11555
+ const accounts = [];
11556
+ for (const raw of list) {
11557
+ if (!raw || typeof raw !== "object")
11558
+ continue;
11559
+ const { id, label, key, models } = raw;
11560
+ if (typeof id !== "string" || !id.trim() || typeof key !== "string" || !key.trim())
11561
+ continue;
11562
+ const modelList = Array.isArray(models) ? models.filter((m) => typeof m === "string" && m.trim() !== "").map((m) => m.trim()) : [];
11563
+ accounts.push({ id: id.trim(), label: typeof label === "string" && label.trim() ? label.trim() : null, key: key.trim(), models: modelList });
11564
+ }
11565
+ if (accounts.length > 0)
11566
+ parsed[provider] = accounts;
11567
+ }
11568
+ return parsed;
11569
+ }
11570
+ 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;
11571
+ var init_provider_accounts = __esm({
11572
+ "../shared/dist/provider-accounts.js"() {
11573
+ "use strict";
11574
+ init_api_model_catalog();
11575
+ DEFAULT_PROVIDER_ACCOUNT_ID = "primary";
11576
+ PROVIDER_ACCOUNT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,39}$/;
11577
+ MAX_PROVIDER_ACCOUNTS = 10;
11578
+ MAX_PROVIDER_ACCOUNT_MODELS = 50;
11579
+ PROVIDER_ACCOUNT_MODEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,120}$/;
11580
+ OPENCODE_GO_DEEPSEEK_GLM_MODELS = [
11581
+ "deepseek-v4.1-flash",
11582
+ "deepseek-v4-pro",
11583
+ "glm-5.3-flash",
11584
+ "glm-5.3",
11585
+ "glm-5.2"
11586
+ ];
11587
+ HOST_PREFIXES = ["opencode-go:", "zai:", "openrouter:"];
10648
11588
  }
10649
11589
  });
10650
11590
 
@@ -10763,6 +11703,25 @@ var init_project_engine_overrides = __esm({
10763
11703
  }
10764
11704
  });
10765
11705
 
11706
+ // ../shared/dist/trailing-slashes.js
11707
+ function stripTrailingSlashes(value) {
11708
+ let end = value.length;
11709
+ while (end > 0 && value.charCodeAt(end - 1) === 47)
11710
+ end--;
11711
+ return end === value.length ? value : value.slice(0, end);
11712
+ }
11713
+ function stripTrailingChars(value, isTrailing) {
11714
+ let end = value.length;
11715
+ while (end > 0 && isTrailing.test(value[end - 1]))
11716
+ end--;
11717
+ return end === value.length ? value : value.slice(0, end);
11718
+ }
11719
+ var init_trailing_slashes = __esm({
11720
+ "../shared/dist/trailing-slashes.js"() {
11721
+ "use strict";
11722
+ }
11723
+ });
11724
+
10766
11725
  // ../shared/dist/gemini-visual.js
10767
11726
  function visualProDailyBudget(raw) {
10768
11727
  const parsed = raw && /^\s*\d{1,6}\s*$/.test(raw) ? Number(raw.trim()) : NaN;
@@ -10776,7 +11735,7 @@ function isRetiredGeminiModel(id) {
10776
11735
  }
10777
11736
  function geminiNativeModelsUrl(base = process.env.GEMINI_API_BASE) {
10778
11737
  const raw = base && base.trim() || GEMINI_OPENAI_BASE;
10779
- const native = raw.replace(/\/openai\/?$/, "/").replace(/\/+$/, "");
11738
+ const native = stripTrailingSlashes(raw.replace(/\/openai\/?$/, "/"));
10780
11739
  return `${native}/models?pageSize=1000`;
10781
11740
  }
10782
11741
  function asRecord(value) {
@@ -10896,6 +11855,7 @@ var KNOWN_INPUT_USD_PER_M, GEMINI_OPENAI_BASE, DEFAULT_GEMINI_SCREEN_MODEL, DEFA
10896
11855
  var init_gemini_visual = __esm({
10897
11856
  "../shared/dist/gemini-visual.js"() {
10898
11857
  "use strict";
11858
+ init_trailing_slashes();
10899
11859
  KNOWN_INPUT_USD_PER_M = {
10900
11860
  "gemini-2.5-flash-lite": 0.1,
10901
11861
  "gemini-3.1-flash-lite": 0.25,
@@ -12238,12 +13198,13 @@ function truncateAtWord(text, max) {
12238
13198
  const slice = trimmed.slice(0, room + 1);
12239
13199
  const lastSpace = slice.lastIndexOf(" ");
12240
13200
  const cut = lastSpace > room * 0.5 ? slice.slice(0, lastSpace) : trimmed.slice(0, room);
12241
- return `${cut.replace(/[\s,;:.\-–—]+$/u, "")}\u2026`;
13201
+ return `${stripTrailingChars(cut, TRAILING_PUNCTUATION)}\u2026`;
12242
13202
  }
12243
- 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;
13203
+ 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;
12244
13204
  var init_canvas_support = __esm({
12245
13205
  "../shared/dist/canvas-support.js"() {
12246
13206
  "use strict";
13207
+ init_trailing_slashes();
12247
13208
  CANVAS_UNSUPPORTED_CODE = "canvas_unsupported";
12248
13209
  CANVAS_UNSUPPORTED_TITLE = "Games and canvas apps aren't supported yet";
12249
13210
  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.";
@@ -12368,6 +13329,7 @@ var init_canvas_support = __esm({
12368
13329
  TOUCH_NONE_RE = /touch-action\s{0,8}:\s{0,8}none/;
12369
13330
  CODE_TAGS = /* @__PURE__ */ new Set(["script", "link", "flt-glass-pane", "flutter-view", "flt-scene-host"]);
12370
13331
  STATIC_STANDARD_MIN_INTERACTIVE = 3;
13332
+ TRAILING_PUNCTUATION = /[\s,;:.\-–—]/u;
12371
13333
  }
12372
13334
  });
12373
13335
 
@@ -12550,22 +13512,22 @@ function joinChainedLines(code) {
12550
13512
  const trimmed = lines[i].trimStart();
12551
13513
  if (last !== -1 && trimmed.startsWith(".") && !trimmed.startsWith("...")) {
12552
13514
  if (!lastStripped) {
12553
- lines[last] = stripLineComment2(lines[last]).trimEnd();
13515
+ lines[last] = stripLineComment3(lines[last]).trimEnd();
12554
13516
  lastStripped = true;
12555
13517
  }
12556
13518
  if (lines[last].length <= MAX_LINE_CHARS)
12557
- lines[last] = `${lines[last]} ${stripLineComment2(trimmed).trimEnd()}`;
13519
+ lines[last] = `${lines[last]} ${stripLineComment3(trimmed).trimEnd()}`;
12558
13520
  lines[i] = "";
12559
13521
  continue;
12560
13522
  }
12561
- if (stripLineComment2(lines[i]).trim()) {
13523
+ if (stripLineComment3(lines[i]).trim()) {
12562
13524
  last = i;
12563
13525
  lastStripped = false;
12564
13526
  }
12565
13527
  }
12566
13528
  return lines.join("\n");
12567
13529
  }
12568
- function stripLineComment2(rawLine) {
13530
+ function stripLineComment3(rawLine) {
12569
13531
  if (rawLine.trimStart().startsWith("//"))
12570
13532
  return "";
12571
13533
  const at = rawLine.indexOf(" // ");
@@ -12581,7 +13543,7 @@ function stateLiteralsOf(line) {
12581
13543
  const out = [];
12582
13544
  for (const m of line.matchAll(STRING_LITERAL_RE)) {
12583
13545
  const text = m[1] ?? m[2] ?? m[3] ?? "";
12584
- if (text.includes("placeholder="))
13546
+ if (PLACEHOLDER_ATTR_RE.test(text))
12585
13547
  continue;
12586
13548
  if (PLACEHOLDER_CALL_TAIL_RE.test(line.slice(Math.max(0, m.index - 40), m.index)))
12587
13549
  continue;
@@ -12614,7 +13576,7 @@ function analyzeReadinessPolling(rawCode) {
12614
13576
  offset += rawLine.length + 1;
12615
13577
  if (rawLine.length > MAX_LINE_CHARS)
12616
13578
  continue;
12617
- const line = stripLineComment2(rawLine);
13579
+ const line = stripLineComment3(rawLine);
12618
13580
  if (!line.trim())
12619
13581
  continue;
12620
13582
  const literals = literalsOf(line);
@@ -12677,7 +13639,7 @@ function formatReadinessPollAdvisory(findings) {
12677
13639
  const where = findings.length === 1 ? "One place" : `${findings.length} places`;
12678
13640
  return `${where} re-check a background state once and assert the finished state at once (${short(first.line, 90)} then ${short(first.assertion, 90)}). ${first.reason}`;
12679
13641
  }
12680
- var READINESS_POLL_MAX_CODE_CHARS, MAX_LINE_CHARS, MAX_FINDINGS, ASSERTION_WINDOW_LINES, MAX_BLOCK_CHARS, MAX_HEAD_CHARS, STRING_LITERAL_RE, RECHECK_NAME_RE, RETRY_NAME_RE, PENDING_STATE_RE, LOCATOR_LINE_RE, CLICK_RE, RELOAD_RE, GOTO_RE, ACTION_RE, EXPECT_RE, NEGATED_RE, PENDING_ENDED_RE, POSITIVE_MATCHER_RE, ZERO_COUNT_RE, TEST_END_RE, TEST_START_RE, POLL_OPEN_RE, PLACEHOLDER_CALL_TAIL_RE, POLL_SHAPE;
13642
+ var READINESS_POLL_MAX_CODE_CHARS, MAX_LINE_CHARS, MAX_FINDINGS, ASSERTION_WINDOW_LINES, MAX_BLOCK_CHARS, MAX_HEAD_CHARS, STRING_LITERAL_RE, RECHECK_NAME_RE, RETRY_NAME_RE, PENDING_STATE_RE, LOCATOR_LINE_RE, CLICK_RE, RELOAD_RE, GOTO_RE, ACTION_RE, EXPECT_RE, NEGATED_RE, PENDING_ENDED_RE, POSITIVE_MATCHER_RE, ZERO_COUNT_RE, TEST_END_RE, TEST_START_RE, POLL_OPEN_RE, PLACEHOLDER_ATTR_RE, PLACEHOLDER_CALL_TAIL_RE, POLL_SHAPE;
12681
13643
  var init_readiness_poll = __esm({
12682
13644
  "../shared/dist/readiness-poll.js"() {
12683
13645
  "use strict";
@@ -12690,7 +13652,7 @@ var init_readiness_poll = __esm({
12690
13652
  STRING_LITERAL_RE = /'((?:\\.|[^'\\\n]){0,300})'|"((?:\\.|[^"\\\n]){0,300})"|`((?:\\.|[^`\\\n]){0,300})`/g;
12691
13653
  RECHECK_NAME_RE = /^\s{0,10}(?:check again|check (?:status|availability|now|for updates|progress)|re-?check(?: status)?|update status|see (?:latest|updates))\s{0,10}(?:\.\.\.|…)?\s{0,10}$/i;
12692
13654
  RETRY_NAME_RE = /^\s{0,10}(?:refresh(?: (?:status|list|page|results|now))?|reload(?: (?:page|results|list))?|try again|retry|load (?:again|latest))\s{0,10}(?:\.\.\.|…)?\s{0,10}$/i;
12693
- PENDING_STATE_RE = /\b(?:searching|checking|sending|pending|processing|queued|in progress|loading|generating|calculating|preparing|verifying|syncing|refreshing|not ready|please wait|waiting)\b|\b[a-z]{3,40}(?<!th)ing(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$/i;
13655
+ PENDING_STATE_RE = /\b(?:searching|checking|sending|pending|processing|queued|in progress|loading|generating|calculating|preparing|verifying|syncing|refreshing|not ready|please wait|waiting)\b|\b(?:saving|uploading|downloading|signing|logging|submitting|creating|updating|deleting|importing|exporting|publishing|connecting|redirecting|authenticating|installing|retrieving|fetching|analy[sz]ing|running|rendering|restarting|building|compiling|booting|provisioning|deploying|indexing|migrating|launching|initiali[sz]ing|optimi[sz]ing|converting|processing)(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$|^\s{0,10}(?:starting|scanning|working|applying|finishing)(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$|^\s{0,10}[a-z]{3,40}(?<!th)ing\s{0,3}(?:\.\.\.|…)\s{0,10}$/i;
12694
13656
  LOCATOR_LINE_RE = /\.(?:getBy[A-Z]\w{0,30}|locator|toHaveText|toContainText)\s{0,20}\(/;
12695
13657
  CLICK_RE = /\.(?:click|dblclick|tap)\s{0,20}\(/;
12696
13658
  RELOAD_RE = /\bpage\.reload\s{0,20}\(/;
@@ -12704,6 +13666,7 @@ var init_readiness_poll = __esm({
12704
13666
  TEST_END_RE = /^\s{0,20}\}\s{0,20}\)\s{0,20};?\s{0,20}$/;
12705
13667
  TEST_START_RE = /\btest(?:\.\w{1,20})?\s{0,20}\(/;
12706
13668
  POLL_OPEN_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}async\b|\bexpect\.poll\s{0,20}\(|\bfor\s{0,20}\(|\bwhile\s{0,20}\(|\bdo\s{0,20}\{/g;
13669
+ PLACEHOLDER_ATTR_RE = /placeholder\s{0,4}[*^$~|]?=/;
12707
13670
  PLACEHOLDER_CALL_TAIL_RE = /getByPlaceholder\s{0,20}\(\s{0,20}$/;
12708
13671
  POLL_SHAPE = "await expect(async () => { await page.reload(); await expect(<the finished state>).toBeVisible({ timeout: 1500 }); }).toPass({ timeout: 20000 });";
12709
13672
  }
@@ -12725,7 +13688,7 @@ function blankBlockComments2(code) {
12725
13688
  from = close + 2;
12726
13689
  }
12727
13690
  }
12728
- function stripLineComment3(rawLine) {
13691
+ function stripLineComment4(rawLine) {
12729
13692
  if (rawLine.trimStart().startsWith("//"))
12730
13693
  return "";
12731
13694
  const at = rawLine.indexOf(" // ");
@@ -12736,7 +13699,7 @@ function logicalLines(code) {
12736
13699
  for (const raw of code.split("\n")) {
12737
13700
  if (raw.length > MAX_LINE_CHARS2)
12738
13701
  continue;
12739
- const line = stripLineComment3(raw).trimEnd();
13702
+ const line = stripLineComment4(raw).trimEnd();
12740
13703
  if (!line.trim())
12741
13704
  continue;
12742
13705
  const trimmed = line.trimStart();
@@ -13098,13 +14061,33 @@ function short4(text, max) {
13098
14061
  const flat = text.replace(/\s+/g, " ").trim();
13099
14062
  return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
13100
14063
  }
14064
+ function stripFractionZeros(s) {
14065
+ let end = s.length;
14066
+ while (end > 0 && s[end - 1] === "0")
14067
+ end--;
14068
+ if (end === s.length)
14069
+ return s;
14070
+ const dot = s.lastIndexOf(".");
14071
+ if (dot < 0)
14072
+ return s;
14073
+ return s.slice(0, end === dot + 1 ? dot : end);
14074
+ }
13101
14075
  function digitsOf(token) {
13102
- return token.replace(/[^\d.]/g, "").replace(/\.0+$|(\.\d*?)0+$/, "$1").replace(/^0+(?=\d)/, "");
14076
+ return stripFractionZeros(token.replace(/[^\d.]/g, "")).replace(/^0+(?=\d)/, "");
13103
14077
  }
13104
14078
  function computes(line) {
13105
14079
  const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX").replace(LITERAL_ARITH_RE, "N");
13106
14080
  return COMPUTE_RE.test(code) || RELATIONAL_RE.test(line);
13107
14081
  }
14082
+ function identifiersIn(line) {
14083
+ const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX");
14084
+ return [code, ...[...line.matchAll(TEMPLATE_EXPR_RE)].map((m) => m[1])].flatMap((part) => part.match(IDENTIFIER_RE) ?? []);
14085
+ }
14086
+ function addTyped(typed, value) {
14087
+ const d = digitsOf(value);
14088
+ if (/\d/.test(d))
14089
+ typed.add(d);
14090
+ }
13108
14091
  function reasonFor4(kind, label, value) {
13109
14092
  const what = kind === "derived-value-shape-only" ? `The ${label} is checked only for its shape, so any amount passes, including a wrongly computed one.` : `The ${label} is checked against a number written into the test (${value}). That number was read off the page, so when the page computes it wrong the check still passes.`;
13110
14093
  return `${what} Instead, ${COMPUTE_SHAPE} If the number is not computed from other values (a list price, an id, a fixed fee), leave it as it is. Keep the existing checks.`;
@@ -13116,25 +14099,32 @@ function analyzeDerivedValues(rawCode) {
13116
14099
  for (const body of testBodies(rawCode)) {
13117
14100
  if (findings.length >= MAX_FINDINGS4)
13118
14101
  break;
13119
- if (body.some(computes))
13120
- continue;
13121
14102
  const typed = /* @__PURE__ */ new Set();
13122
14103
  const definitions = /* @__PURE__ */ new Map();
14104
+ const computed = /* @__PURE__ */ new Set();
14105
+ const usesComputed = (text) => identifiersIn(text).some((id) => computed.has(id));
13123
14106
  for (const line of body) {
13124
14107
  if (FILL_RE2.test(line) && !EXPECT_RE3.test(line)) {
13125
- for (const m of line.matchAll(/['"`]([^'"`\n]{0,40})['"`]/g)) {
13126
- const d = digitsOf(m[1]);
13127
- if (/\d/.test(d))
13128
- typed.add(d);
13129
- }
14108
+ for (const m of line.matchAll(QUOTED_RE))
14109
+ addTyped(typed, m[1]);
14110
+ const arg = FILL_ARG_RE.exec(line)?.[1];
14111
+ const literal = arg === void 0 ? null : LITERAL_VALUE_RE.exec(definitions.get(arg) ?? "");
14112
+ const value = literal?.slice(1).find((v) => v !== void 0);
14113
+ if (value !== void 0)
14114
+ addTyped(typed, value);
13130
14115
  }
13131
14116
  const def = DEFINITION_RE.exec(line);
13132
- if (def)
14117
+ if (def) {
13133
14118
  definitions.set(def[1], def[2]);
14119
+ if (computes(def[2]) || usesComputed(def[2]))
14120
+ computed.add(def[1]);
14121
+ }
13134
14122
  }
13135
14123
  for (const line of body) {
13136
14124
  if (!EXPECT_RE3.test(line) || !VALUE_MATCHER_RE2.test(line) || /\.not\s{0,20}\./.test(line))
13137
14125
  continue;
14126
+ if (computes(line) || usesComputed(line))
14127
+ continue;
13138
14128
  const subject = EXPECT_SUBJECT_RE.exec(line)?.[1];
13139
14129
  const label = DERIVED_WORD_RE.exec(`${line} ${(subject && definitions.get(subject)) ?? ""}`)?.[0];
13140
14130
  if (!label)
@@ -13164,7 +14154,7 @@ function formatDerivedValueAdvisory(findings) {
13164
14154
  const where = findings.length === 1 ? "One computed value is" : `${findings.length} computed values are`;
13165
14155
  return `${where} checked against a copied number or only its shape (${short4(first.line, 90)}). ${first.reason}`;
13166
14156
  }
13167
- var MAX_FINDINGS4, EXPECT_RE3, DERIVED_WORD_RE, VALUE_MATCHER_RE2, MONEY_TOKEN_RE, SHAPE_ARG_RE, FILL_RE2, EXPECT_MESSAGE_RE, STRING_RE, REGEX_LITERAL_RE, COMPUTE_RE, RELATIONAL_RE, DEFINITION_RE, EXPECT_SUBJECT_RE, LITERAL_ARITH_RE, COMPUTE_SHAPE;
14157
+ var MAX_FINDINGS4, EXPECT_RE3, DERIVED_WORD_RE, VALUE_MATCHER_RE2, MONEY_TOKEN_RE, SHAPE_ARG_RE, FILL_RE2, EXPECT_MESSAGE_RE, STRING_RE, REGEX_LITERAL_RE, COMPUTE_RE, RELATIONAL_RE, DEFINITION_RE, EXPECT_SUBJECT_RE, LITERAL_ARITH_RE, TEMPLATE_EXPR_RE, IDENTIFIER_RE, QUOTED_RE, FILL_ARG_RE, LITERAL_VALUE_RE, COMPUTE_SHAPE;
13168
14158
  var init_derived_value_check = __esm({
13169
14159
  "../shared/dist/derived-value-check.js"() {
13170
14160
  "use strict";
@@ -13184,6 +14174,11 @@ var init_derived_value_check = __esm({
13184
14174
  DEFINITION_RE = /\b(?:const|let)\s{1,10}([\w$]{1,60})\s{0,10}=\s{0,10}([^\n]{0,300})/;
13185
14175
  EXPECT_SUBJECT_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}([\w$]{1,60})/;
13186
14176
  LITERAL_ARITH_RE = /\b\d[\d_.]{0,15}(?:\s{0,5}[*/+-]\s{0,5}\d[\d_.]{0,15}){1,8}\b/g;
14177
+ TEMPLATE_EXPR_RE = /\$\{([^{}\n]{0,200})\}/g;
14178
+ IDENTIFIER_RE = /[A-Za-z_$][\w$]{0,60}/g;
14179
+ QUOTED_RE = /['"`]([^'"`\n]{0,40})['"`]/g;
14180
+ FILL_ARG_RE = /\.(?:fill|type|pressSequentially|selectOption)\s{0,20}\(\s{0,20}([A-Za-z_$][\w$]{0,60})\s{0,20}[,)]/;
14181
+ LITERAL_VALUE_RE = /^\s{0,10}(?:'([^'\n]{0,40})'|"([^"\n]{0,40})"|`([^`$\n]{0,40})`|(\d[\d_,]{0,15}(?:\.\d{1,4})?))\s{0,10};?\s{0,10}$/;
13187
14182
  COMPUTE_SHAPE = "compute the expected value in the test from its inputs (the values the test typed, or each input read from the page) and compare: const toNumber = (s) => Number(s.replace(/[^\\d.-]/g, '')); const unit = toNumber(await <unit price locator>.innerText()); then expect(toNumber(await <shown value locator>.innerText())).toBeCloseTo(unit * quantity, 2); for several lines, read them all and sum them with reduce. For a comma-decimal price like \u20AC1.234,56, drop the dots and turn the comma into a point before Number, since the replace above would read it as 1.23456.";
13188
14183
  }
13189
14184
  });
@@ -13296,7 +14291,7 @@ function findExpectCalls(code) {
13296
14291
  if (!subj)
13297
14292
  continue;
13298
14293
  const rest = code.slice(subj.end + 1, subj.end + 1 + 80);
13299
- const mm = /^\s*((?:\.\s*not)?\s*\.\s*(\w+))\s{0,20}\(/.exec(rest);
14294
+ const mm = /^\s*((?:\.\s*not\s*)?\.\s*(\w+))\s{0,20}\(/.exec(rest);
13300
14295
  if (!mm)
13301
14296
  continue;
13302
14297
  const argStart = subj.end + 1 + mm[0].length;
@@ -15218,6 +16213,133 @@ function formatStaleAfterSubmitAdvisory(rawCode) {
15218
16213
  const more = described.length > 3 ? ` (+${described.length - 3} more)` : "";
15219
16214
  return `stale-after-submit: ${described.slice(0, 3).join("; ")}${more}; these pass before the request lands and still pass if the server rejects or ignores the save. Wait for the result instead: page.waitForResponse around the click, or assert something the submit creates (the new row, the confirmation message, a path with an end anchor such as /\\/orders\\/\\d+$/)`;
15220
16215
  }
16216
+ function regexLiteralEnd4(text, at) {
16217
+ let inClass = false;
16218
+ for (let i = at + 1; i < text.length; i++) {
16219
+ const ch = text[i];
16220
+ if (ch === "\n")
16221
+ return -1;
16222
+ if (ch === "\\") {
16223
+ i++;
16224
+ continue;
16225
+ }
16226
+ if (inClass) {
16227
+ if (ch === "]")
16228
+ inClass = false;
16229
+ continue;
16230
+ }
16231
+ if (ch === "[") {
16232
+ inClass = true;
16233
+ continue;
16234
+ }
16235
+ if (ch === "/")
16236
+ return i + 1;
16237
+ }
16238
+ return -1;
16239
+ }
16240
+ function startsWithRegexLiteral(text) {
16241
+ const t = text.trimStart();
16242
+ return t[0] === "/" && t[1] !== "/" && t[1] !== "*" && regexLiteralEnd4(t, 0) !== -1;
16243
+ }
16244
+ function isBareRegexLiteral(text) {
16245
+ const t = text.trimStart();
16246
+ if (t[0] !== "/" || t[1] === "/" || t[1] === "*")
16247
+ return false;
16248
+ const end = regexLiteralEnd4(t, 0);
16249
+ if (end === -1)
16250
+ return false;
16251
+ let i = end;
16252
+ while (i < t.length && /[a-z]/.test(t[i]))
16253
+ i++;
16254
+ while (i < t.length && /\s/.test(t[i]))
16255
+ i++;
16256
+ return i >= t.length || t[i] === ",";
16257
+ }
16258
+ function regexDeclaredNames(code) {
16259
+ const names = /* @__PURE__ */ new Set();
16260
+ 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;
16261
+ let m;
16262
+ while ((m = decl.exec(code)) !== null)
16263
+ names.add(m[1]);
16264
+ return names;
16265
+ }
16266
+ function afterFirstArg(args) {
16267
+ let depth = 0;
16268
+ let quote = null;
16269
+ for (let i = 0; i < args.length; i++) {
16270
+ const ch = args[i];
16271
+ if (quote) {
16272
+ if (ch === "\\") {
16273
+ i++;
16274
+ continue;
16275
+ }
16276
+ if (ch === quote)
16277
+ quote = null;
16278
+ continue;
16279
+ }
16280
+ if (ch === '"' || ch === "'" || ch === "`") {
16281
+ quote = ch;
16282
+ continue;
16283
+ }
16284
+ if (ch === "(" || ch === "[" || ch === "{")
16285
+ depth++;
16286
+ else if (ch === ")" || ch === "]" || ch === "}")
16287
+ depth--;
16288
+ else if (ch === "," && depth === 0)
16289
+ return args.slice(i + 1);
16290
+ }
16291
+ return "";
16292
+ }
16293
+ function analyzeRegexMatchers(rawCode) {
16294
+ if (!rawCode || typeof rawCode !== "string")
16295
+ return [];
16296
+ const code = stripComments2(rawCode.slice(0, MAX_LINT_INPUT));
16297
+ const out = [];
16298
+ const re = /\bexpect(?:\.soft)?\s{0,20}\(/g;
16299
+ let m;
16300
+ let examined = 0;
16301
+ let line = 1;
16302
+ let lineAt2 = 0;
16303
+ let regexNames;
16304
+ while ((m = re.exec(code)) !== null && examined++ < MAX_CALLS && out.length < MAX_REGEX_MATCHER_FINDINGS) {
16305
+ const subj = readBalanced(code, m.index + m[0].length);
16306
+ if (!subj)
16307
+ continue;
16308
+ const mm = /^\s{0,20}(\.\s{0,20}not)?\s{0,20}\.\s{0,20}(\w{1,40})\s{0,20}\(/.exec(code.slice(subj.end + 1, subj.end + 1 + 80));
16309
+ if (!mm || !REGEX_EQUALITY_MATCHERS.has(mm[2]))
16310
+ continue;
16311
+ const args = readBalanced(code, subj.end + 1 + mm[0].length);
16312
+ if (!args)
16313
+ continue;
16314
+ const expected = mm[2] === "toHaveJSProperty" ? afterFirstArg(args.text) : args.text;
16315
+ if (!isBareRegexLiteral(expected))
16316
+ continue;
16317
+ if (mm[2] !== "toHaveJSProperty" && mm[2] !== "toContain") {
16318
+ const subject = subj.text.trim();
16319
+ if (startsWithRegexLiteral(subject) || /^new\s+RegExp\b/.test(subject))
16320
+ continue;
16321
+ if (/^[A-Za-z_$][\w$]{0,80}$/.test(subject)) {
16322
+ regexNames ??= regexDeclaredNames(code);
16323
+ if (regexNames.has(subject))
16324
+ continue;
16325
+ }
16326
+ }
16327
+ for (let i = code.indexOf("\n", lineAt2); i !== -1 && i < m.index; i = code.indexOf("\n", i + 1)) {
16328
+ line++;
16329
+ lineAt2 = i + 1;
16330
+ }
16331
+ out.push({ line, matcher: mm[2], negated: mm[1] !== void 0 });
16332
+ }
16333
+ return out;
16334
+ }
16335
+ function formatRegexMatcherNote(findings) {
16336
+ const lines = findings.map((f) => {
16337
+ 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";
16338
+ 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"}.`;
16339
+ });
16340
+ return `${lines.join("\n")}
16341
+ 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.`;
16342
+ }
15221
16343
  function lintTestQuality(rawCode, opts = {}) {
15222
16344
  if (!rawCode || typeof rawCode !== "string")
15223
16345
  return [];
@@ -15225,6 +16347,10 @@ function lintTestQuality(rawCode, opts = {}) {
15225
16347
  return lintTestData(rawCode, { pageContacts: opts.pageContacts });
15226
16348
  if (opts.only === "test-data-blocking")
15227
16349
  return analyzeTestData(rawCode, { pageContacts: opts.pageContacts }).blocking;
16350
+ if (opts.only === "regex-matcher") {
16351
+ const found = analyzeRegexMatchers(rawCode);
16352
+ return found.length > 0 ? [formatRegexMatcherNote(found)] : [];
16353
+ }
15228
16354
  const warnings = [];
15229
16355
  if (rawCode.length > MAX_LINT_INPUT) {
15230
16356
  warnings.push(`The code is ${Math.round(rawCode.length / 1024)} KB; the quality lint checked only the first ${MAX_LINT_INPUT / 1e3} KB. Split it into smaller tests (one behaviour per test).`);
@@ -15499,7 +16625,7 @@ function specItemsFromTestPlans(testPlans) {
15499
16625
  }
15500
16626
  return [...new Set(out)].slice(0, 200);
15501
16627
  }
15502
- var READ_ONLY_TAGS, PRODUCTION_READ_ONLY_GUARD_TEMPLATE, HOST_AGNOSTIC_URL_TEMPLATE, TEST_QUALITY_CONVENTIONS, NON_PROD_HOST_LABEL, NON_PROD_HOST_TOKEN, TRANSIENT_TEXT, STRING_LITERAL, FIRST_LITERAL, GET_BY_TEXT_LITERAL2, ROLE_NAME_LITERAL2, MAX_LINT_INPUT, MAX_BALANCED_SCAN, MAX_CALLS, MAX_SEEN_SCAN, ACTION_CALL, BY_NAME_LOCATOR, ACTION_AFTER, MAX_CONTACTS, MAX_SNAPSHOT_SCAN, PHONE_IN_TEXT, EMAIL_IN_TEXT, TEL_HREF, MAILTO_HREF, FIELD_LINE, INBOX_EMAIL_READ, MAIL_READ_CALL, RESERVED_EMAIL_TLD, PHONE_LABEL, NON_PHONE_NUMBER_LABEL, IDENTITY_LABEL, LOOKUP_LABEL, NON_NAME_SUFFIX_LABEL, DATE_LIKE, SIGNUP_ACTION_NAME, SIGNUP_SUBMIT_NAME, LOGIN_ACTION_NAME, SIGNUP_GOTO, LOGIN_GOTO, MINTED_INBOX_ALIAS, STAMP_EVAL, RECORDING_VALUE_PLACEHOLDERS, NEGATIVE_TEST_TITLE, NEGATIVE_SIGNUP_ASSERT, STORED_LOGIN_RE, CONFIRM_CLICK_RE, SAVE_UPDATE_RE, NOT_A_CONFIRM_RE, ENTITY_WORDS, CREATE_ENTITY_CLICK_RE, RESTORE_CLICK_RE, MAX_ACCOUNT_ACTIONS, MAX_TRY_BLOCKS, MAX_STATEMENT_LOOKBACK, MAX_FINDINGS5, MAX_TRY_BODY, AUTH_REGION_START, AUTH_REGION_END, INBOX_REGION_START, INBOX_REGION_END, ACCOUNT_ROUTE_RE, ENTITY_WORD_RES, PERSIST_CALL_RE, FILL_IDENT_ARG_RE, PERSIST_ARGS_RE, HOST_IN_URL_ARG, URL_WAIT_TOKEN, GLOB_ESCAPED, URL_WAIT_MAX_PATH, URL_WAIT_MAX_GLOB_STARS, UNREACHED_MAX_PATHS, UNREACHED_MATCH_BUDGET, STALE_TOKEN, RESULT_WAIT, WRITE_CALL_PRESENT, SUBMIT_CALL_PRESENT, TEST_CALL, VALUE_WRITE_CALLS, SUBMIT_CALLS, DISTINCT_PAGE_CALLS, OWN_CONTROL_MATCHERS, OUTCOME_CONTENT_MATCHERS, OUTCOME_STATE_MATCHERS, ANY_CONTENT_ARG, ROOT_SUBJECT, LITERAL_SUBJECT, MATCHER_AFTER_EXPECT, STALE_MAX_EVENTS, CHECKED, UNCHECKED, squash, NOT_JUDGED, STALE_REASON_TEXT, LIVE_WRITE_NAME, BUNDLED_CHECKS_BEFORE_WRITE, STOP, SYNONYMS, AREA_TOKENS;
16628
+ var READ_ONLY_TAGS, PRODUCTION_READ_ONLY_GUARD_TEMPLATE, HOST_AGNOSTIC_URL_TEMPLATE, TEST_QUALITY_CONVENTIONS, NON_PROD_HOST_LABEL, NON_PROD_HOST_TOKEN, TRANSIENT_TEXT, STRING_LITERAL, FIRST_LITERAL, GET_BY_TEXT_LITERAL2, ROLE_NAME_LITERAL2, MAX_LINT_INPUT, MAX_BALANCED_SCAN, MAX_CALLS, MAX_SEEN_SCAN, ACTION_CALL, BY_NAME_LOCATOR, ACTION_AFTER, MAX_CONTACTS, MAX_SNAPSHOT_SCAN, PHONE_IN_TEXT, EMAIL_IN_TEXT, TEL_HREF, MAILTO_HREF, FIELD_LINE, INBOX_EMAIL_READ, MAIL_READ_CALL, RESERVED_EMAIL_TLD, PHONE_LABEL, NON_PHONE_NUMBER_LABEL, IDENTITY_LABEL, LOOKUP_LABEL, NON_NAME_SUFFIX_LABEL, DATE_LIKE, SIGNUP_ACTION_NAME, SIGNUP_SUBMIT_NAME, LOGIN_ACTION_NAME, SIGNUP_GOTO, LOGIN_GOTO, MINTED_INBOX_ALIAS, STAMP_EVAL, RECORDING_VALUE_PLACEHOLDERS, NEGATIVE_TEST_TITLE, NEGATIVE_SIGNUP_ASSERT, STORED_LOGIN_RE, CONFIRM_CLICK_RE, SAVE_UPDATE_RE, NOT_A_CONFIRM_RE, ENTITY_WORDS, CREATE_ENTITY_CLICK_RE, RESTORE_CLICK_RE, MAX_ACCOUNT_ACTIONS, MAX_TRY_BLOCKS, MAX_STATEMENT_LOOKBACK, MAX_FINDINGS5, MAX_TRY_BODY, AUTH_REGION_START, AUTH_REGION_END, INBOX_REGION_START, INBOX_REGION_END, ACCOUNT_ROUTE_RE, ENTITY_WORD_RES, PERSIST_CALL_RE, FILL_IDENT_ARG_RE, PERSIST_ARGS_RE, HOST_IN_URL_ARG, URL_WAIT_TOKEN, GLOB_ESCAPED, URL_WAIT_MAX_PATH, URL_WAIT_MAX_GLOB_STARS, UNREACHED_MAX_PATHS, UNREACHED_MATCH_BUDGET, STALE_TOKEN, RESULT_WAIT, WRITE_CALL_PRESENT, SUBMIT_CALL_PRESENT, TEST_CALL, VALUE_WRITE_CALLS, SUBMIT_CALLS, DISTINCT_PAGE_CALLS, OWN_CONTROL_MATCHERS, OUTCOME_CONTENT_MATCHERS, OUTCOME_STATE_MATCHERS, ANY_CONTENT_ARG, ROOT_SUBJECT, LITERAL_SUBJECT, MATCHER_AFTER_EXPECT, STALE_MAX_EVENTS, CHECKED, UNCHECKED, squash, NOT_JUDGED, STALE_REASON_TEXT, LIVE_WRITE_NAME, BUNDLED_CHECKS_BEFORE_WRITE, MAX_REGEX_MATCHER_FINDINGS, REGEX_EQUALITY_MATCHERS, STOP, SYNONYMS, AREA_TOKENS;
15503
16629
  var init_test_authoring_quality = __esm({
15504
16630
  "../shared/dist/test-authoring-quality.js"() {
15505
16631
  "use strict";
@@ -15731,6 +16857,8 @@ var init_test_authoring_quality = __esm({
15731
16857
  };
15732
16858
  LIVE_WRITE_NAME = /\b(?:submit|place|book|reserve|confirm|pay|send|checkout|order|register|sign\s?up|create account)\b/i;
15733
16859
  BUNDLED_CHECKS_BEFORE_WRITE = 5;
16860
+ MAX_REGEX_MATCHER_FINDINGS = 10;
16861
+ REGEX_EQUALITY_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual", "toContain", "toHaveJSProperty"]);
15734
16862
  STOP = /* @__PURE__ */ new Set([
15735
16863
  "a",
15736
16864
  "an",
@@ -15969,7 +17097,7 @@ function normalizeBugPath(route) {
15969
17097
  if (hash >= 0 && path[hash + 1] !== "/")
15970
17098
  path = path.slice(0, hash);
15971
17099
  const segments = path.toLowerCase().split("/").map((segment) => /\d/.test(segment) ? ":id" : segment);
15972
- const joined = segments.join("/").replace(/\/+$/, "");
17100
+ const joined = stripTrailingSlashes(segments.join("/"));
15973
17101
  return joined || (path.startsWith("/") ? "/" : "");
15974
17102
  }
15975
17103
  function isGenericConsoleMessage(message) {
@@ -15982,14 +17110,17 @@ function requestSignatures(text) {
15982
17110
  const statuses = [...text.matchAll(HTTP_STATUS)].map((m) => ({ status: m[1], at: m.index ?? 0, end: (m.index ?? 0) + m[0].length }));
15983
17111
  if (statuses.length === 0)
15984
17112
  return [];
15985
- const paths = [...text.matchAll(PATH_IN_TEXT)].map((m) => ({
15986
- method: m[1] ?? null,
15987
- path: normalizeBugPath((m[2] ?? m[3] ?? "").replace(/[.,;:]+$/, "")),
15988
- // Before normalisation, which turns a segment with a digit (favicon-32x32.png) into :id.
15989
- incidental: isIncidentalAssetPath((m[2] ?? m[3] ?? "").replace(/[.,;:]+$/, "").split(/[?#]/, 1)[0].toLowerCase()),
15990
- at: m.index ?? 0,
15991
- end: (m.index ?? 0) + m[0].length
15992
- })).filter((p) => p.path.length > 1);
17113
+ const paths = [...text.matchAll(PATH_IN_TEXT)].map((m) => {
17114
+ const rawPath = stripTrailingChars(m[2] ?? m[3] ?? "", PATH_TRAILING_PUNCTUATION);
17115
+ return {
17116
+ method: m[1] ?? null,
17117
+ path: normalizeBugPath(rawPath),
17118
+ // Before normalisation, which turns a segment with a digit (favicon-32x32.png) into :id.
17119
+ incidental: isIncidentalAssetPath(rawPath.split(/[?#]/, 1)[0].toLowerCase()),
17120
+ at: m.index ?? 0,
17121
+ end: (m.index ?? 0) + m[0].length
17122
+ };
17123
+ }).filter((p) => p.path.length > 1);
15993
17124
  const out = /* @__PURE__ */ new Map();
15994
17125
  for (const { status, at, end } of statuses) {
15995
17126
  const lead = STATUS_TO_PATH.exec(text.slice(end, end + 40));
@@ -16099,10 +17230,12 @@ function dedupeSiteWideSuspectedBugs(drafts) {
16099
17230
  delete plans.suspectedBugs;
16100
17231
  }
16101
17232
  }
16102
- 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;
17233
+ 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;
16103
17234
  var init_survey_bug_signature = __esm({
16104
17235
  "../shared/dist/survey-bug-signature.js"() {
16105
17236
  "use strict";
17237
+ init_trailing_slashes();
17238
+ PATH_TRAILING_PUNCTUATION = /[.,;:]/;
16106
17239
  SIGNATURE_SOURCE_MAX_CHARS = 800;
16107
17240
  UUID = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g;
16108
17241
  PREFIXED_ID = /\b[a-z]{1,8}[-_]\d+\b/g;
@@ -17383,9 +18516,9 @@ function lastLocatorLabels(chain) {
17383
18516
  if (t)
17384
18517
  labels.push(t[1]);
17385
18518
  }
17386
- const has = optionText(last.args, HAS_TEXT_OPTION_RE);
17387
- if (has !== null)
17388
- labels.push(has);
18519
+ const has2 = optionText(last.args, HAS_TEXT_OPTION_RE);
18520
+ if (has2 !== null)
18521
+ labels.push(has2);
17389
18522
  }
17390
18523
  return labels.map((l) => l.replace(/^[\s^(?:]+/, "").trim());
17391
18524
  }
@@ -17433,7 +18566,7 @@ function pathIdsIn(text) {
17433
18566
  function findCounterIdPins(code) {
17434
18567
  if (code.length > CLOCK_MAX_CODE_CHARS)
17435
18568
  return [];
17436
- const clean2 = stripLineComments(blankBlockComments3(code));
18569
+ const clean3 = stripLineComments(blankBlockComments3(code));
17437
18570
  const fillValues = [];
17438
18571
  const candidates = [];
17439
18572
  let seenWrite = false;
@@ -17441,24 +18574,24 @@ function findCounterIdPins(code) {
17441
18574
  CALL_RE.lastIndex = 0;
17442
18575
  let m;
17443
18576
  let calls = 0;
17444
- while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean2)) !== null) {
18577
+ while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean3)) !== null) {
17445
18578
  const name = m[1];
17446
18579
  const open = m.index + m[0].length - 1;
17447
18580
  if (VALUE_WRITES.has(name)) {
17448
18581
  seenWrite = true;
17449
- const args2 = argsAt(clean2, open);
18582
+ const args2 = argsAt(clean3, open);
17450
18583
  if (args2 !== null)
17451
18584
  fillValues.push(...staticStrings(args2));
17452
18585
  continue;
17453
18586
  }
17454
18587
  if (createIndex === -1) {
17455
- if (name === "click" && seenWrite && isCreateClick(clean2, m.index))
18588
+ if (name === "click" && seenWrite && isCreateClick(clean3, m.index))
17456
18589
  createIndex = m.index;
17457
18590
  continue;
17458
18591
  }
17459
18592
  if (name === "click")
17460
18593
  continue;
17461
- const args = argsAt(clean2, open);
18594
+ const args = argsAt(clean3, open);
17462
18595
  if (args === null)
17463
18596
  continue;
17464
18597
  const isUrl = URL_SINKS.has(name);
@@ -17477,7 +18610,7 @@ function findCounterIdPins(code) {
17477
18610
  }
17478
18611
  if (createIndex === -1)
17479
18612
  return [];
17480
- const before = clean2.slice(0, createIndex);
18613
+ const before = clean3.slice(0, createIndex);
17481
18614
  let pathIdsBefore = null;
17482
18615
  const findings = [];
17483
18616
  const seen = /* @__PURE__ */ new Set();
@@ -17501,7 +18634,7 @@ function findCounterIdPins(code) {
17501
18634
  continue;
17502
18635
  }
17503
18636
  }
17504
- findings.push({ kind: c.kind, literal: c.literal, line: lineAt(clean2, c.index) });
18637
+ findings.push({ kind: c.kind, literal: c.literal, line: lineAt(clean3, c.index) });
17505
18638
  }
17506
18639
  return findings;
17507
18640
  }
@@ -17796,7 +18929,7 @@ var init_suspicious_expectation = __esm({
17796
18929
  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;
17797
18930
  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;
17798
18931
  STUCK_VETO_RE = new RegExp(`${NOT_STUCK_RE.source}|${LISTS_STUCK_RE.source}`, "i");
17799
- 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;
18932
+ 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;
17800
18933
  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)";
17801
18934
  ELLIPSIS = "(?:\\.{3}|\u2026)";
17802
18935
  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;
@@ -17952,7 +19085,7 @@ function outcomeNamedByAssertion(outcome, code) {
17952
19085
  function namedByStatements(outcome, statements) {
17953
19086
  const text = outcomeText(outcome);
17954
19087
  if (outcome.type === "url") {
17955
- const routes = (text.match(ROUTE_TERM) ?? []).map((r) => r.replace(/[.,;:!?)]+$/, "")).filter((r) => r.length > 1);
19088
+ const routes = (text.match(ROUTE_TERM) ?? []).map((r) => stripTrailingChars(r, ROUTE_TRAILING_PUNCTUATION)).filter((r) => r.length > 1);
17956
19089
  return statements.some((stmt) => {
17957
19090
  if (!/\.(?:waitForURL|toHaveURL)\s{0,20}\(/.test(stmt))
17958
19091
  return false;
@@ -18124,7 +19257,7 @@ function evaluatePromotionGate(input, checks = PROMOTION_QUALITY_CHECKS) {
18124
19257
  ...suspiciousTagged ? { suspiciousPass: { mode: suspicious, covered: suspiciousCovered, wouldHold: suspiciousHold } } : {}
18125
19258
  };
18126
19259
  }
18127
- 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;
19260
+ 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;
18128
19261
  var init_promotion_quality_gate = __esm({
18129
19262
  "../shared/dist/promotion-quality-gate.js"() {
18130
19263
  "use strict";
@@ -18139,17 +19272,21 @@ var init_promotion_quality_gate = __esm({
18139
19272
  init_run_classification();
18140
19273
  init_expected_outcome_coverage();
18141
19274
  init_expect_call();
19275
+ init_trailing_slashes();
18142
19276
  SOURCE_RANK = { generator: 0, classifier: 0, healer: 1, human: 2 };
18143
19277
  strengthCheck = {
18144
19278
  id: "assertion-strength",
18145
- run({ code, strengthGate, flagContradictedAdditions }) {
18146
- const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true });
19279
+ run({ code, tags, strengthGate, flagContradictedAdditions, lastActionProof }) {
19280
+ const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true, lastActionProof });
18147
19281
  if (verdict.severity === "route-bug")
18148
19282
  return [{ check: "route-bug" }];
18149
- if (verdict.severity !== "block")
19283
+ const authDowngrade = verdict.severity === "downgrade" && tags.some((t) => t.toLowerCase() === "@auth");
19284
+ if (verdict.severity !== "block" && !authDowngrade)
18150
19285
  return [];
18151
19286
  const revealed = strengthGate?.severity === "none" && strengthGate.revealEvidence === "revealed" && strengthGate.source !== "heal";
18152
- return revealed ? [] : [{ check: "strength-block" }];
19287
+ if (revealed)
19288
+ return [];
19289
+ return [{ check: authDowngrade ? "strength-downgrade-auth" : "strength-block" }];
18153
19290
  }
18154
19291
  };
18155
19292
  STRING_CONST = /\b(?:const|let|var)\s{1,20}([A-Za-z_$][\w$]{0,60})\s{0,20}=\s{0,20}(?:'([^'\\\n]{0,300})'|"([^"\\\n]{0,300})"|`([^`\\$\n]{0,300})`)\s{0,20}[;\n]/g;
@@ -18158,7 +19295,7 @@ var init_promotion_quality_gate = __esm({
18158
19295
  MAX_STATEMENTS = 500;
18159
19296
  MAX_STATEMENT_CHARS = 400;
18160
19297
  LITERAL_OR_IDENTIFIER = /'(?:[^'\\\n]|\\.){0,500}'|"(?:[^"\\\n]|\\.){0,500}"|`(?:[^`\\]|\\.){0,2000}`|(?<![\w$.])[A-Za-z_$][\w$]{0,60}/g;
18161
- 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}\(/;
19298
+ 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}\(/;
18162
19299
  FEEDBACK_REGION = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/i;
18163
19300
  ONE_SHOT_MESSAGE_REASON = "one-shot success/error banner text";
18164
19301
  FEEDBACK_REGION_ALL = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/gi;
@@ -18217,6 +19354,7 @@ var init_promotion_quality_gate = __esm({
18217
19354
  "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}\)/
18218
19355
  };
18219
19356
  ROUTE_TERM = /\/[A-Za-z0-9_./?&=#-]{1,200}/g;
19357
+ ROUTE_TRAILING_PUNCTUATION = /[.,;:!?)]/;
18220
19358
  QUOTED_LITERAL = /'([^'\\\n]{3,200})'|"([^"\\\n]{3,200})"|`([^`\\$\n]{3,200})`/g;
18221
19359
  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;
18222
19360
  MAX_OUTCOME_STATEMENTS = 300;
@@ -18336,8 +19474,8 @@ ${goalText}`);
18336
19474
  return count3 > 0 ? [{ check: "locator-fragment", detail: String(count3), soft: true }] : [];
18337
19475
  }
18338
19476
  };
18339
- 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;
18340
- NEXT_ACTION = /\.(?:goto|reload|click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|selectOption|setInputFiles|dragTo|clear)\s*\(/;
19477
+ 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;
19478
+ NEXT_ACTION = /\.(?:goto|reload|click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|setChecked|selectOption|setInputFiles|dragTo|dragAndDrop|clear)\s*\(/;
18341
19479
  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;
18342
19480
  MAX_FILTER_LINES = 2e3;
18343
19481
  MAX_FILTER_LINE_CHARS = 600;
@@ -18452,7 +19590,7 @@ ${goalText}`);
18452
19590
  });
18453
19591
 
18454
19592
  // ../shared/dist/regex-alternation.js
18455
- function closingParens(source) {
19593
+ function closingParens2(source) {
18456
19594
  const close = new Int32Array(source.length).fill(-1);
18457
19595
  const open = [];
18458
19596
  let inClass = false;
@@ -18477,7 +19615,7 @@ function closingParens(source) {
18477
19615
  return close;
18478
19616
  }
18479
19617
  function alternatesAtTop(source) {
18480
- const close = closingParens(source);
19618
+ const close = closingParens2(source);
18481
19619
  let start = source.startsWith("^") ? 1 : 0;
18482
19620
  let end = source.endsWith("$") ? source.length - 1 : source.length;
18483
19621
  while (start < end && source[start] === "(" && close[start] === end - 1) {
@@ -18526,6 +19664,124 @@ var init_page_evidence = __esm({
18526
19664
  }
18527
19665
  });
18528
19666
 
19667
+ // ../shared/dist/stated-limits.js
19668
+ function validDate(v) {
19669
+ const [y, m, d] = v.split("-").map(Number);
19670
+ const t = new Date(Date.UTC(y, m - 1, d));
19671
+ return t.getUTCFullYear() === y && t.getUTCMonth() === m - 1 && t.getUTCDate() === d;
19672
+ }
19673
+ function isoFor(type, v) {
19674
+ const re = DATE_RE[type];
19675
+ if (!re || !re.test(v))
19676
+ return false;
19677
+ if (type === "date")
19678
+ return validDate(v);
19679
+ if (type === "datetime-local")
19680
+ return validDate(v.slice(0, 10)) && Number(v.slice(11, 13)) < 24 && Number(v.slice(14, 16)) < 60;
19681
+ if (type === "time")
19682
+ return Number(v.slice(0, 2)) < 24 && Number(v.slice(3, 5)) < 60;
19683
+ if (type === "month")
19684
+ return Number(v.slice(5, 7)) >= 1 && Number(v.slice(5, 7)) <= 12;
19685
+ return Number(v.slice(6, 8)) >= 1 && Number(v.slice(6, 8)) <= 53;
19686
+ }
19687
+ function clean2(raw) {
19688
+ return typeof raw === "string" ? raw.trim() : "";
19689
+ }
19690
+ function statedLimitsFromAttrs(attrs, inputType) {
19691
+ const type = (inputType ?? "").trim().toLowerCase();
19692
+ const limits = [];
19693
+ const add = (k, v) => {
19694
+ limits.push({ k, v });
19695
+ };
19696
+ if (NUMBER_TYPES.has(type)) {
19697
+ for (const [attr, k] of [["min", "min"], ["max", "max"]]) {
19698
+ const v = clean2(attrs[attr]);
19699
+ if (NUMBER_RE.test(v))
19700
+ add(k, v);
19701
+ }
19702
+ const step = clean2(attrs.step);
19703
+ if (NUMBER_RE.test(step) && Number(step) > 0)
19704
+ add("step", step);
19705
+ } else if (type in DATE_RE) {
19706
+ for (const [attr, k] of [["min", "min"], ["max", "max"]]) {
19707
+ const v = clean2(attrs[attr]);
19708
+ if (isoFor(type, v))
19709
+ add(k, v);
19710
+ }
19711
+ } else if (TEXT_TYPES.has(type)) {
19712
+ for (const [attr, k] of [["minlength", "minLength"], ["maxlength", "maxLength"]]) {
19713
+ const v = clean2(attrs[attr]);
19714
+ if (LENGTH_RE.test(v))
19715
+ add(k, String(Number(v)));
19716
+ }
19717
+ const pattern = cleanPattern(clean2(attrs.pattern).slice(0, STATED_PATTERN_MAX));
19718
+ if (pattern)
19719
+ add("pattern", pattern);
19720
+ }
19721
+ return limits.length > 0 ? { v: STATED_LIMITS_VERSION, limits } : null;
19722
+ }
19723
+ function cleanPattern(text) {
19724
+ return text.replace(/[\u0000-\u001f\u007f]/g, "").replace(/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g, "");
19725
+ }
19726
+ function readStatedLimits(stored) {
19727
+ if (!stored || typeof stored !== "object" || Array.isArray(stored))
19728
+ return [];
19729
+ const rec = stored;
19730
+ if (rec.v !== STATED_LIMITS_VERSION || !Array.isArray(rec.limits))
19731
+ return [];
19732
+ const out = [];
19733
+ const seen = /* @__PURE__ */ new Set();
19734
+ for (const item of rec.limits.slice(0, MAX_LIMITS * 2)) {
19735
+ if (!item || typeof item !== "object")
19736
+ continue;
19737
+ const { k, v } = item;
19738
+ if (typeof k !== "string" || typeof v !== "string" || !KINDS.includes(k) || seen.has(k))
19739
+ continue;
19740
+ if (v.length === 0 || v.length > STATED_PATTERN_MAX)
19741
+ continue;
19742
+ const text = k === "pattern" ? cleanPattern(v) : v;
19743
+ if (text === "")
19744
+ continue;
19745
+ if (k === "minLength" || k === "maxLength") {
19746
+ if (!LENGTH_RE.test(v))
19747
+ continue;
19748
+ } else if (k === "step") {
19749
+ if (!NUMBER_RE.test(v))
19750
+ continue;
19751
+ } else if (k === "min" || k === "max") {
19752
+ if (!NUMBER_RE.test(v) && !Object.values(DATE_RE).some((re) => re.test(v)))
19753
+ continue;
19754
+ }
19755
+ seen.add(k);
19756
+ out.push({ k, v: text });
19757
+ }
19758
+ return out;
19759
+ }
19760
+ function statedLimitsRecord(limits) {
19761
+ return limits.length > 0 ? { v: STATED_LIMITS_VERSION, limits } : null;
19762
+ }
19763
+ var STATED_LIMITS_VERSION, STATED_PATTERN_MAX, KINDS, MAX_LIMITS, TEXT_TYPES, NUMBER_TYPES, DATE_RE, NUMBER_RE, LENGTH_RE;
19764
+ var init_stated_limits = __esm({
19765
+ "../shared/dist/stated-limits.js"() {
19766
+ "use strict";
19767
+ STATED_LIMITS_VERSION = 1;
19768
+ STATED_PATTERN_MAX = 200;
19769
+ KINDS = ["min", "max", "minLength", "maxLength", "step", "pattern"];
19770
+ MAX_LIMITS = KINDS.length;
19771
+ TEXT_TYPES = /* @__PURE__ */ new Set(["text", "search", "email", "url", "tel", "password", "textarea", ""]);
19772
+ NUMBER_TYPES = /* @__PURE__ */ new Set(["number", "range"]);
19773
+ DATE_RE = {
19774
+ date: /^\d{4}-\d{2}-\d{2}$/,
19775
+ "datetime-local": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?$/,
19776
+ time: /^\d{2}:\d{2}(?::\d{2})?$/,
19777
+ month: /^\d{4}-\d{2}$/,
19778
+ week: /^\d{4}-W\d{2}$/
19779
+ };
19780
+ NUMBER_RE = /^-?\d{1,15}(?:\.\d{1,10})?$/;
19781
+ LENGTH_RE = /^\d{1,7}$/;
19782
+ }
19783
+ });
19784
+
18529
19785
  // ../shared/dist/index.js
18530
19786
  var dist_exports = {};
18531
19787
  __export(dist_exports, {
@@ -18533,9 +19789,11 @@ __export(dist_exports, {
18533
19789
  ACTION_BATCH_MAX_SIZE: () => ACTION_BATCH_MAX_SIZE,
18534
19790
  ADDON_DEFINITIONS: () => ADDON_DEFINITIONS,
18535
19791
  ADMIN_EMAIL_LIMITS: () => ADMIN_EMAIL_LIMITS,
19792
+ ADMIN_FORCE_CANCELLED_RUN_ERROR: () => ADMIN_FORCE_CANCELLED_RUN_ERROR,
18536
19793
  ADMIN_VIEW_DEFAULT_RETURN_TO: () => ADMIN_VIEW_DEFAULT_RETURN_TO,
18537
19794
  AGENT_AI_PROVIDER_PREFIXES: () => AGENT_AI_PROVIDER_PREFIXES,
18538
19795
  AI_CREDIT_MARGIN: () => AI_CREDIT_MARGIN,
19796
+ ALLOWED_PROVIDER_CHAIN_HOPS: () => ALLOWED_PROVIDER_CHAIN_HOPS,
18539
19797
  API_AUTH_HEADERS_ENV_KEY: () => API_AUTH_HEADERS_ENV_KEY,
18540
19798
  API_MODEL_CATALOG: () => API_MODEL_CATALOG,
18541
19799
  API_MODEL_FIELDS: () => API_MODEL_FIELDS,
@@ -18556,6 +19814,7 @@ __export(dist_exports, {
18556
19814
  AUDIO_CHUNK_DURATION_MS: () => AUDIO_CHUNK_DURATION_MS,
18557
19815
  AUTH_PATH_SIGNATURE: () => AUTH_PATH_SIGNATURE,
18558
19816
  AUTH_PROVIDER_HOST_SIGNATURES: () => AUTH_PROVIDER_HOST_SIGNATURES,
19817
+ AUTH_SESSION_GENERATION_RE: () => AUTH_SESSION_GENERATION_RE,
18559
19818
  AUTH_STATE_EXPIRY_SKEW_MS: () => AUTH_STATE_EXPIRY_SKEW_MS,
18560
19819
  AUTH_TAG_COOKIE_STATE: () => AUTH_TAG_COOKIE_STATE,
18561
19820
  AUTH_TAG_LOGIN_BEARER: () => AUTH_TAG_LOGIN_BEARER,
@@ -18574,12 +19833,14 @@ __export(dist_exports, {
18574
19833
  BUG_TEST_BROKEN_REASON: () => BUG_TEST_BROKEN_REASON,
18575
19834
  BUG_TEST_BROKEN_TAG: () => BUG_TEST_BROKEN_TAG,
18576
19835
  CACHE_PRICE_PER_M: () => CACHE_PRICE_PER_M,
19836
+ CANCELLED_REASON_PREFIXES: () => CANCELLED_REASON_PREFIXES,
18577
19837
  CANVAS_UNSUPPORTED_BODY: () => CANVAS_UNSUPPORTED_BODY,
18578
19838
  CANVAS_UNSUPPORTED_CODE: () => CANVAS_UNSUPPORTED_CODE,
18579
19839
  CANVAS_UNSUPPORTED_NOT_CHARGED: () => CANVAS_UNSUPPORTED_NOT_CHARGED,
18580
19840
  CANVAS_UNSUPPORTED_OVERRIDE_HINT: () => CANVAS_UNSUPPORTED_OVERRIDE_HINT,
18581
19841
  CANVAS_UNSUPPORTED_TITLE: () => CANVAS_UNSUPPORTED_TITLE,
18582
19842
  CAPTURE_JSON_STRUCTURE_LIMITS: () => CAPTURE_JSON_STRUCTURE_LIMITS,
19843
+ CHAIN_CANCELLED_RUN_ERROR: () => CHAIN_CANCELLED_RUN_ERROR,
18583
19844
  CHAT_FAILOVER_KEY: () => CHAT_FAILOVER_KEY,
18584
19845
  CLAUDE_CLI_DEFAULT_EFFORT: () => CLAUDE_CLI_DEFAULT_EFFORT,
18585
19846
  CLAUDE_CLI_DEFAULT_MODEL: () => CLAUDE_CLI_DEFAULT_MODEL,
@@ -18616,6 +19877,7 @@ __export(dist_exports, {
18616
19877
  DEFAULT_GEMINI_SCREEN_MODEL: () => DEFAULT_GEMINI_SCREEN_MODEL,
18617
19878
  DEFAULT_JSON_STRUCTURE_LIMITS: () => DEFAULT_JSON_STRUCTURE_LIMITS,
18618
19879
  DEFAULT_PLATFORM_DB_POOL_SIZE: () => DEFAULT_PLATFORM_DB_POOL_SIZE,
19880
+ DEFAULT_PROVIDER_ACCOUNT_ID: () => DEFAULT_PROVIDER_ACCOUNT_ID,
18619
19881
  DEFAULT_SHARED_DB_POOL_SIZE: () => DEFAULT_SHARED_DB_POOL_SIZE,
18620
19882
  DEFAULT_STT_MODEL: () => DEFAULT_STT_MODEL,
18621
19883
  DEFAULT_STT_MODEL_BY_PROVIDER: () => DEFAULT_STT_MODEL_BY_PROVIDER,
@@ -18632,6 +19894,7 @@ __export(dist_exports, {
18632
19894
  ENVIRONMENTAL_ERROR_STORED_PREFIXES: () => ENVIRONMENTAL_ERROR_STORED_PREFIXES,
18633
19895
  EVIDENCE_NOTE_PREFIXES: () => EVIDENCE_NOTE_PREFIXES,
18634
19896
  EXPECT_CALL_RE: () => EXPECT_CALL_RE,
19897
+ FEATURE_ROW_RUN_WINDOW: () => FEATURE_ROW_RUN_WINDOW,
18635
19898
  FLOW_PHASE_LABELS: () => FLOW_PHASE_LABELS,
18636
19899
  FLOW_PHASE_TYPES: () => FLOW_PHASE_TYPES,
18637
19900
  FLOW_SERVER_TYPES: () => FLOW_SERVER_TYPES,
@@ -18688,6 +19951,8 @@ __export(dist_exports, {
18688
19951
  LIVE_FRAME_STALE_MS: () => LIVE_FRAME_STALE_MS,
18689
19952
  LIVE_FRAME_UNFOCUSED_INTERVAL_MS: () => LIVE_FRAME_UNFOCUSED_INTERVAL_MS,
18690
19953
  LIVE_THUMBNAIL_MAX_BASE64_BYTES: () => LIVE_THUMBNAIL_MAX_BASE64_BYTES,
19954
+ MAX_BALANCED_SCAN: () => MAX_BALANCED_SCAN,
19955
+ MAX_CALLS: () => MAX_CALLS,
18691
19956
  MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
18692
19957
  MAX_DEEPLINK_LENGTH: () => MAX_DEEPLINK_LENGTH,
18693
19958
  MAX_EMAIL_ADDRESS_LENGTH: () => MAX_EMAIL_ADDRESS_LENGTH,
@@ -18697,6 +19962,8 @@ __export(dist_exports, {
18697
19962
  MAX_ORG_NAME_LENGTH: () => MAX_ORG_NAME_LENGTH,
18698
19963
  MAX_PASSWORD_BYTES: () => MAX_PASSWORD_BYTES,
18699
19964
  MAX_PLAYWRIGHT_CODE_LENGTH: () => MAX_PLAYWRIGHT_CODE_LENGTH,
19965
+ MAX_PROVIDER_ACCOUNTS: () => MAX_PROVIDER_ACCOUNTS,
19966
+ MAX_PROVIDER_ACCOUNT_MODELS: () => MAX_PROVIDER_ACCOUNT_MODELS,
18700
19967
  MAX_SURVEY_ENTRY_URLS: () => MAX_SURVEY_ENTRY_URLS,
18701
19968
  MAX_SURVEY_ENTRY_URL_LENGTH: () => MAX_SURVEY_ENTRY_URL_LENGTH,
18702
19969
  MCP_ARMY_LIMITS: () => MCP_ARMY_LIMITS,
@@ -18732,6 +19999,7 @@ __export(dist_exports, {
18732
19999
  MUSE_ENGINE_DEFAULT_MODEL: () => MUSE_ENGINE_DEFAULT_MODEL,
18733
20000
  MUSE_ENGINE_MODELS: () => MUSE_ENGINE_MODELS,
18734
20001
  NOREPLY_EMAIL: () => NOREPLY_EMAIL,
20002
+ OPENCODE_GO_DEEPSEEK_GLM_MODELS: () => OPENCODE_GO_DEEPSEEK_GLM_MODELS,
18735
20003
  OPENCODE_GO_MODEL_PATTERN: () => OPENCODE_GO_MODEL_PATTERN,
18736
20004
  OPENCODE_GO_PREFIX: () => OPENCODE_GO_PREFIX,
18737
20005
  OPENROUTER_MAX_CALL_COST_USD: () => OPENROUTER_MAX_CALL_COST_USD,
@@ -18751,10 +20019,12 @@ __export(dist_exports, {
18751
20019
  PLAN_FEATURES: () => PLAN_FEATURES,
18752
20020
  PLAN_FEATURE_LOCKED_CODE: () => PLAN_FEATURE_LOCKED_CODE,
18753
20021
  PLAN_ORDER: () => PLAN_ORDER,
20022
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR: () => PLATFORM_ADMIN_CANCELLED_RUN_ERROR,
18754
20023
  PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
18755
20024
  PLATFORM_DEFAULT_PROVIDER: () => PLATFORM_DEFAULT_PROVIDER,
18756
20025
  PLATFORM_PLAN_BILLING: () => PLATFORM_PLAN_BILLING,
18757
20026
  PLATFORM_SERVER_APPLICATION_NAME: () => PLATFORM_SERVER_APPLICATION_NAME,
20027
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR: () => PLATFORM_SUPPORT_CANCELLED_RUN_ERROR,
18758
20028
  POST_SPECIAL_TRIAL_DAYS: () => POST_SPECIAL_TRIAL_DAYS,
18759
20029
  PRESET_DIRECT_FALLBACK: () => PRESET_DIRECT_FALLBACK,
18760
20030
  PRODUCTION_READ_ONLY_GUARD_TEMPLATE: () => PRODUCTION_READ_ONLY_GUARD_TEMPLATE,
@@ -18766,17 +20036,25 @@ __export(dist_exports, {
18766
20036
  PROJECT_OVERRIDE_FIELDS: () => PROJECT_OVERRIDE_FIELDS,
18767
20037
  PROMOTION_GATE_MAX_CODE_CHARS: () => PROMOTION_GATE_MAX_CODE_CHARS,
18768
20038
  PROMOTION_QUALITY_CHECKS: () => PROMOTION_QUALITY_CHECKS,
20039
+ PROVIDER_ACCOUNT_ID_PATTERN: () => PROVIDER_ACCOUNT_ID_PATTERN,
20040
+ PROVIDER_ACCOUNT_MODEL_PATTERN: () => PROVIDER_ACCOUNT_MODEL_PATTERN,
18769
20041
  PROVIDER_CHAINS: () => PROVIDER_CHAINS,
20042
+ PROVIDER_CHAINS_SETTING_MAX_CHARS: () => PROVIDER_CHAINS_SETTING_MAX_CHARS,
20043
+ PROVIDER_CHAIN_LEADS: () => PROVIDER_CHAIN_LEADS,
20044
+ PROVIDER_CHAIN_MAX_ENTRIES: () => PROVIDER_CHAIN_MAX_ENTRIES,
20045
+ PROVIDER_HOST_NOT_OFFERED: () => PROVIDER_HOST_NOT_OFFERED,
18770
20046
  PUBLIC_PLANS: () => PUBLIC_PLANS,
18771
20047
  QUARANTINE_CLOSE_TAGS: () => QUARANTINE_CLOSE_TAGS,
18772
20048
  READINESS_POLL_MAX_CODE_CHARS: () => READINESS_POLL_MAX_CODE_CHARS,
18773
20049
  READ_ONLY_TAGS: () => READ_ONLY_TAGS,
18774
20050
  RECHECKED_NOTES: () => RECHECKED_NOTES,
18775
20051
  RECORDING_VALUE_PLACEHOLDERS: () => RECORDING_VALUE_PLACEHOLDERS,
20052
+ RENDER_IDENTITY_MAX_LENGTH: () => RENDER_IDENTITY_MAX_LENGTH,
18776
20053
  REPOSITORY_CLIS: () => REPOSITORY_CLIS,
18777
20054
  RESERVED_PROJECT_KEYS: () => RESERVED_PROJECT_KEYS,
18778
20055
  RESULT_STRUCTURE_TOO_COSTLY_CODE: () => RESULT_STRUCTURE_TOO_COSTLY_CODE,
18779
20056
  RETRIED_ATTEMPTS_KEY: () => RETRIED_ATTEMPTS_KEY,
20057
+ RETRY_HELD_RUN_ERROR: () => RETRY_HELD_RUN_ERROR,
18780
20058
  RUNNER_INTERRUPT_ERROR_PREFIX: () => RUNNER_INTERRUPT_ERROR_PREFIX,
18781
20059
  RUN_INTERRUPTED_ERROR_PREFIXES: () => RUN_INTERRUPTED_ERROR_PREFIXES,
18782
20060
  RUN_INTERRUPTION_NOT_RETRIED_MARKERS: () => RUN_INTERRUPTION_NOT_RETRIED_MARKERS,
@@ -18789,6 +20067,8 @@ __export(dist_exports, {
18789
20067
  SHARED_SERVER_APPLICATION_NAME: () => SHARED_SERVER_APPLICATION_NAME,
18790
20068
  SPEND_SOURCE_KEY: () => SPEND_SOURCE_KEY,
18791
20069
  SPEND_SOURCE_SERVER: () => SPEND_SOURCE_SERVER,
20070
+ STATED_LIMITS_VERSION: () => STATED_LIMITS_VERSION,
20071
+ STATED_PATTERN_MAX: () => STATED_PATTERN_MAX,
18792
20072
  STATIC_STANDARD_MIN_INTERACTIVE: () => STATIC_STANDARD_MIN_INTERACTIVE,
18793
20073
  STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
18794
20074
  SUCCESS_NOTICE_TEXT: () => SUCCESS_NOTICE_TEXT,
@@ -18805,6 +20085,7 @@ __export(dist_exports, {
18805
20085
  SURVEY_NOTES_RENDER_MAX_CHARS: () => SURVEY_NOTES_RENDER_MAX_CHARS,
18806
20086
  SURVEY_NOTE_KEYS: () => SURVEY_NOTE_KEYS,
18807
20087
  SUSPICIOUS_PASS_TAG: () => SUSPICIOUS_PASS_TAG,
20088
+ TEST_DID_NOT_RUN_ERROR: () => TEST_DID_NOT_RUN_ERROR,
18808
20089
  TEST_QUALITY_CONVENTIONS: () => TEST_QUALITY_CONVENTIONS,
18809
20090
  TEST_USER_ENV_KEYS: () => TEST_USER_ENV_KEYS,
18810
20091
  TEST_VARIABLE_SECRET_KEY_PATTERN: () => TEST_VARIABLE_SECRET_KEY_PATTERN,
@@ -18817,23 +20098,33 @@ __export(dist_exports, {
18817
20098
  UI_VARIANTS: () => UI_VARIANTS,
18818
20099
  UNEXPECTED_PASS_ERROR_PREFIX: () => UNEXPECTED_PASS_ERROR_PREFIX,
18819
20100
  USER_CANCELLED_RUN_ERROR: () => USER_CANCELLED_RUN_ERROR,
20101
+ VISUAL_MASK_SELECTORS_MAX: () => VISUAL_MASK_SELECTORS_MAX,
20102
+ VISUAL_MASK_SELECTOR_FORBIDDEN: () => VISUAL_MASK_SELECTOR_FORBIDDEN,
20103
+ VISUAL_MASK_SELECTOR_MAX_LENGTH: () => VISUAL_MASK_SELECTOR_MAX_LENGTH,
20104
+ VISUAL_V1_IDENTITY: () => VISUAL_V1_IDENTITY,
18820
20105
  VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
18821
20106
  WEB_PROXY_CHAIN_HEADER: () => WEB_PROXY_CHAIN_HEADER,
18822
20107
  WITHHELD_CREDENTIAL_TEXT: () => WITHHELD_CREDENTIAL_TEXT,
20108
+ ZAI_MODEL_PATTERN: () => ZAI_MODEL_PATTERN,
20109
+ ZAI_PREFIX: () => ZAI_PREFIX,
20110
+ accountServesModel: () => accountServesModel,
18823
20111
  accumulateStatements: () => accumulateStatements,
18824
20112
  adoptBrokerFailover: () => adoptBrokerFailover,
18825
20113
  aiCostToCreditCents: () => aiCostToCreditCents,
20114
+ allowedProviderChainHosts: () => allowedProviderChainHosts,
18826
20115
  alternatesAtTop: () => alternatesAtTop,
18827
20116
  analyzeDerivedValues: () => analyzeDerivedValues,
18828
20117
  analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
18829
20118
  analyzePersistenceReread: () => analyzePersistenceReread,
18830
20119
  analyzeReadinessPolling: () => analyzeReadinessPolling,
20120
+ analyzeRegexMatchers: () => analyzeRegexMatchers,
18831
20121
  analyzeSharedAccountUse: () => analyzeSharedAccountUse,
18832
20122
  analyzeStaleAfterSubmit: () => analyzeStaleAfterSubmit,
18833
20123
  analyzeTestData: () => analyzeTestData,
18834
20124
  analyzeUnreachedUrlWaits: () => analyzeUnreachedUrlWaits,
18835
20125
  analyzeUrlWaits: () => analyzeUrlWaits,
18836
20126
  analyzeWriteResponse: () => analyzeWriteResponse,
20127
+ androidResourceOwners: () => androidResourceOwners,
18837
20128
  apiModelCardsFromConfig: () => apiModelCardsFromConfig,
18838
20129
  apiModelsForProvider: () => apiModelsForProvider,
18839
20130
  apiProviderModelLabel: () => apiProviderModelLabel,
@@ -18844,6 +20135,7 @@ __export(dist_exports, {
18844
20135
  applyLaunchSpecialPlan: () => applyLaunchSpecialPlan,
18845
20136
  asPlainExpect: () => asPlainExpect,
18846
20137
  assertionOnlyCode: () => assertionOnlyCode,
20138
+ assertionStrengthHoistedReadsOn: () => assertionStrengthHoistedReadsOn,
18847
20139
  assertionsReadVisibleContent: () => assertionsReadVisibleContent,
18848
20140
  assessCodeOutcomeCoverage: () => assessCodeOutcomeCoverage,
18849
20141
  assessTitleCoverage: () => assessTitleCoverage,
@@ -18927,6 +20219,7 @@ __export(dist_exports, {
18927
20219
  deepgramSpeak: () => deepgramSpeak,
18928
20220
  deepgramTranscribe: () => deepgramTranscribe,
18929
20221
  defaultConfirmModel: () => defaultConfirmModel,
20222
+ defaultProviderChainEntries: () => defaultProviderChainEntries,
18930
20223
  defaultScreenModel: () => defaultScreenModel,
18931
20224
  defaultVoiceForTtsModel: () => defaultVoiceForTtsModel,
18932
20225
  deriveAuthPlan: () => deriveAuthPlan,
@@ -18969,6 +20262,7 @@ __export(dist_exports, {
18969
20262
  extractOutcomeSearchTerms: () => extractOutcomeSearchTerms,
18970
20263
  featureForAiCallType: () => featureForAiCallType,
18971
20264
  featureForMcpTool: () => featureForMcpTool,
20265
+ featureRowRunStatus: () => featureRowRunStatus,
18972
20266
  findBracedClause: () => findBracedClause,
18973
20267
  findCounterIdPins: () => findCounterIdPins,
18974
20268
  findCredentialTextLeftover: () => findCredentialTextLeftover,
@@ -18991,6 +20285,7 @@ __export(dist_exports, {
18991
20285
  formatPersistenceRereadAdvisory: () => formatPersistenceRereadAdvisory,
18992
20286
  formatPoolBudgetLine: () => formatPoolBudgetLine,
18993
20287
  formatReadinessPollAdvisory: () => formatReadinessPollAdvisory,
20288
+ formatRegexMatcherNote: () => formatRegexMatcherNote,
18994
20289
  formatStaleAfterSubmitAdvisory: () => formatStaleAfterSubmitAdvisory,
18995
20290
  formatSuspiciousExpectation: () => formatSuspiciousExpectation,
18996
20291
  formatUrlWaitAdvisory: () => formatUrlWaitAdvisory,
@@ -19016,6 +20311,7 @@ __export(dist_exports, {
19016
20311
  getPlanChangeType: () => getPlanChangeType,
19017
20312
  getPlanDefinition: () => getPlanDefinition,
19018
20313
  getPlanLabel: () => getPlanLabel,
20314
+ getProviderChainOverrides: () => getProviderChainOverrides,
19019
20315
  getTrialDaysRemaining: () => getTrialDaysRemaining,
19020
20316
  googleBrokerNavigationUrl: () => googleBrokerNavigationUrl,
19021
20317
  googleBrokerUnavailableLine: () => googleBrokerUnavailableLine,
@@ -19032,9 +20328,11 @@ __export(dist_exports, {
19032
20328
  hasOpenCodeGoPrefix: () => hasOpenCodeGoPrefix,
19033
20329
  hasOpenRouterPrefix: () => hasOpenRouterPrefix,
19034
20330
  hasPlatformLocator: () => hasPlatformLocator,
20331
+ hasProviderChainLeadsByDefault: () => hasProviderChainLeadsByDefault,
19035
20332
  hasReadOnlyMarker: () => hasReadOnlyMarker,
19036
20333
  hasSurveyNotes: () => hasSurveyNotes,
19037
20334
  hasTestFailContract: () => hasTestFailContract,
20335
+ hasZaiPrefix: () => hasZaiPrefix,
19038
20336
  healMobileTestSteps: () => healMobileTestSteps,
19039
20337
  healthRunWhere: () => healthRunWhere,
19040
20338
  heldCodeRecord: () => heldCodeRecord,
@@ -19056,8 +20354,11 @@ __export(dist_exports, {
19056
20354
  isAuthTokenName: () => isAuthTokenName,
19057
20355
  isBlockedEnvVar: () => isBlockedEnvVar,
19058
20356
  isBrowserCliEngine: () => isBrowserCliEngine,
20357
+ isBrowserVersion: () => isBrowserVersion,
19059
20358
  isBugTestBrokenError: () => isBugTestBrokenError,
20359
+ isCancelledReason: () => isCancelledReason,
19060
20360
  isCanvasUnsupportedMessage: () => isCanvasUnsupportedMessage,
20361
+ isChainablePrimary: () => isChainablePrimary,
19061
20362
  isCliEngineModelId: () => isCliEngineModelId,
19062
20363
  isCliEngineSpec: () => isCliEngineSpec,
19063
20364
  isCodeFactNote: () => isCodeFactNote,
@@ -19081,6 +20382,7 @@ __export(dist_exports, {
19081
20382
  isIdentityMobileXpath: () => isIdentityMobileXpath,
19082
20383
  isIdentityProviderSurface: () => isIdentityProviderSurface,
19083
20384
  isIncidentalAssetPath: () => isIncidentalAssetPath,
20385
+ isIntentionalNegativeAuthTest: () => isIntentionalNegativeAuthTest,
19084
20386
  isIpOrLocalhost: () => isIpOrLocalhost,
19085
20387
  isKnownPageType: () => isKnownPageType,
19086
20388
  isLaunchSpecialActive: () => isLaunchSpecialActive,
@@ -19108,6 +20410,7 @@ __export(dist_exports, {
19108
20410
  isRunInterruptedError: () => isRunInterruptedError,
19109
20411
  isRunnableStep: () => isRunnableStep,
19110
20412
  isSafeDeepLink: () => isSafeDeepLink,
20413
+ isSafeMaskSelector: () => isSafeMaskSelector,
19111
20414
  isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
19112
20415
  isSelectableApiModel: () => isSelectableApiModel,
19113
20416
  isSelfServePlan: () => isSelfServePlan,
@@ -19138,6 +20441,7 @@ __export(dist_exports, {
19138
20441
  isValidSttProvider: () => isValidSttProvider,
19139
20442
  isValidTtsModel: () => isValidTtsModel,
19140
20443
  isValidTtsVoiceId: () => isValidTtsVoiceId,
20444
+ isZaiModelId: () => isZaiModelId,
19141
20445
  jsEscapedForms: () => jsEscapedForms,
19142
20446
  jsonStructureRefusalOf: () => jsonStructureRefusalOf,
19143
20447
  jsonStructureVerify: () => jsonStructureVerify,
@@ -19170,6 +20474,7 @@ __export(dist_exports, {
19170
20474
  normalizeFinishReason: () => normalizeFinishReason,
19171
20475
  normalizeNoteText: () => normalizeNoteText,
19172
20476
  normalizePageType: () => normalizePageType,
20477
+ normalizeProviderAccountModels: () => normalizeProviderAccountModels,
19173
20478
  normalizeTtsModel: () => normalizeTtsModel,
19174
20479
  normalizeTtsVoiceId: () => normalizeTtsVoiceId,
19175
20480
  noteChatFailover: () => noteChatFailover,
@@ -19186,6 +20491,7 @@ __export(dist_exports, {
19186
20491
  parseApiCardModelUpdate: () => parseApiCardModelUpdate,
19187
20492
  parseAppTimezone: () => parseAppTimezone,
19188
20493
  parseAuditGroupScopeId: () => parseAuditGroupScopeId,
20494
+ parseAuthSessionStamp: () => parseAuthSessionStamp,
19189
20495
  parseBoolFlag: () => parseBoolFlag,
19190
20496
  parseDbPoolSize: () => parseDbPoolSize,
19191
20497
  parseDeepgramListenResponse: () => parseDeepgramListenResponse,
@@ -19193,13 +20499,18 @@ __export(dist_exports, {
19193
20499
  parseGeminiModelsPayload: () => parseGeminiModelsPayload,
19194
20500
  parseGoogleAuthorizeUrl: () => parseGoogleAuthorizeUrl,
19195
20501
  parseGoogleGisRequestUrl: () => parseGoogleGisRequestUrl,
20502
+ parseLastActionProofMode: () => parseLastActionProofMode,
19196
20503
  parseLimitParam: () => parseLimitParam,
19197
20504
  parseOffsetParam: () => parseOffsetParam,
19198
20505
  parsePageParam: () => parsePageParam,
19199
20506
  parsePgIntSetting: () => parsePgIntSetting,
19200
20507
  parseProjectEngineOverrides: () => parseProjectEngineOverrides,
19201
20508
  parsePromotionGateMode: () => parsePromotionGateMode,
20509
+ parseProviderAccountLists: () => parseProviderAccountLists,
20510
+ parseProviderChainOverrides: () => parseProviderChainOverrides,
19202
20511
  parseRecheckClearMode: () => parseRecheckClearMode,
20512
+ parseRenderIdentity: () => parseRenderIdentity,
20513
+ parseStoredProviderChainOverrides: () => parseStoredProviderChainOverrides,
19203
20514
  parseSurveyEntryUrls: () => parseSurveyEntryUrls,
19204
20515
  parseSuspiciousPassMode: () => parseSuspiciousPassMode,
19205
20516
  parseTaskModelUpdate: () => parseTaskModelUpdate,
@@ -19229,13 +20540,20 @@ __export(dist_exports, {
19229
20540
  profileKeyRoute: () => profileKeyRoute,
19230
20541
  projectAppHosts: () => projectAppHosts,
19231
20542
  providerChainFor: () => providerChainFor,
20543
+ providerChainHostOf: () => providerChainHostOf,
20544
+ providerChainHostProvider: () => providerChainHostProvider,
20545
+ providerChainLeadsFor: () => providerChainLeadsFor,
20546
+ providerChainModelKey: () => providerChainModelKey,
19232
20547
  providerChainPrimariesForHop: () => providerChainPrimariesForHop,
20548
+ providerHostPlan: () => providerHostPlan,
20549
+ readBalanced: () => readBalanced,
19233
20550
  readChatFailover: () => readChatFailover,
19234
20551
  readChatReasoningTokens: () => readChatReasoningTokens,
19235
20552
  readChatUsage: () => readChatUsage,
19236
20553
  readEnabledEngines: () => readEnabledEngines,
19237
20554
  readProjectEngineOverrides: () => readProjectEngineOverrides,
19238
20555
  readRetriedAttempts: () => readRetriedAttempts,
20556
+ readStatedLimits: () => readStatedLimits,
19239
20557
  readStaticPageCanvasSignals: () => readStaticPageCanvasSignals,
19240
20558
  readSurveyEntryUrls: () => readSurveyEntryUrls,
19241
20559
  readSurveyNotes: () => readSurveyNotes,
@@ -19248,6 +20566,7 @@ __export(dist_exports, {
19248
20566
  regexLiteralEnd: () => regexLiteralEnd2,
19249
20567
  regexOpensAfter: () => regexOpensAfter,
19250
20568
  registrableDomain: () => registrableDomain,
20569
+ renderIdentity: () => renderIdentity,
19251
20570
  renderSurveyNotesForPrompt: () => renderSurveyNotesForPrompt,
19252
20571
  repairPageType: () => repairPageType,
19253
20572
  replaceBlockComments: () => replaceBlockComments,
@@ -19295,11 +20614,14 @@ __export(dist_exports, {
19295
20614
  selectorCandidatesForStep: () => selectorCandidatesForStep,
19296
20615
  selectorForValue: () => selectorForValue,
19297
20616
  setAgenticCliRuntime: () => setAgenticCliRuntime,
20617
+ setProviderChainOverrides: () => setProviderChainOverrides,
19298
20618
  signupPlanFor: () => signupPlanFor,
19299
20619
  simplifyMobileWanderingInPlace: () => simplifyMobileWanderingInPlace,
19300
20620
  siteOf: () => siteOf,
19301
20621
  specItemsFromTestPlans: () => specItemsFromTestPlans,
19302
20622
  splitAnswerLines: () => splitAnswerLines,
20623
+ statedLimitsFromAttrs: () => statedLimitsFromAttrs,
20624
+ statedLimitsRecord: () => statedLimitsRecord,
19303
20625
  stepAppliesToPlatform: () => stepAppliesToPlatform,
19304
20626
  stepConsoleLines: () => stepConsoleLines,
19305
20627
  stepIsDivergentForPlatform: () => stepIsDivergentForPlatform,
@@ -19311,7 +20633,11 @@ __export(dist_exports, {
19311
20633
  stripCodeFactPrefix: () => stripCodeFactPrefix,
19312
20634
  stripCoverageComments: () => stripCoverageComments,
19313
20635
  stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
20636
+ stripFractionZeros: () => stripFractionZeros,
20637
+ stripLineComment: () => stripLineComment,
19314
20638
  stripLoginScaffolding: () => stripLoginScaffolding,
20639
+ stripTrailingChars: () => stripTrailingChars,
20640
+ stripTrailingSlashes: () => stripTrailingSlashes,
19315
20641
  strongerNeedsReviewSource: () => strongerNeedsReviewSource,
19316
20642
  sttModelsForProvider: () => sttModelsForProvider,
19317
20643
  summarizeHealth: () => summarizeHealth,
@@ -19328,10 +20654,14 @@ __export(dist_exports, {
19328
20654
  ttsFallbackModel: () => ttsFallbackModel,
19329
20655
  ttsProviderForModel: () => ttsProviderForModel,
19330
20656
  unexpectedPassError: () => unexpectedPassError,
20657
+ v1RenderIdentity: () => v1RenderIdentity,
19331
20658
  validateAppMapDocument: () => validateAppMapDocument,
19332
20659
  validateAssertionStrength: () => validateAssertionStrength,
19333
20660
  validateEngineSpec: () => validateEngineSpec,
20661
+ validateProviderChainEntries: () => validateProviderChainEntries,
20662
+ validateProviderChainsSetting: () => validateProviderChainsSetting,
19334
20663
  validateTaskModelId: () => validateTaskModelId,
20664
+ visualBaselinesEnabled: () => visualBaselinesEnabled,
19335
20665
  visualProDailyBudget: () => visualProDailyBudget,
19336
20666
  voiceMatchesTtsModel: () => voiceMatchesTtsModel,
19337
20667
  voicesForTtsModel: () => voicesForTtsModel,
@@ -19339,7 +20669,8 @@ __export(dist_exports, {
19339
20669
  withReviewSource: () => withReviewSource,
19340
20670
  withoutNotChargedClaim: () => withoutNotChargedClaim,
19341
20671
  withoutServerOnlySpendFields: () => withoutServerOnlySpendFields,
19342
- withoutSurveyNotes: () => withoutSurveyNotes
20672
+ withoutSurveyNotes: () => withoutSurveyNotes,
20673
+ zaiWireModel: () => zaiWireModel
19343
20674
  });
19344
20675
  var init_dist = __esm({
19345
20676
  "../shared/dist/index.js"() {
@@ -19381,6 +20712,7 @@ var init_dist = __esm({
19381
20712
  init_admin_view_return_to();
19382
20713
  init_auth_plan();
19383
20714
  init_auth_block_detector();
20715
+ init_auth_session_stamp();
19384
20716
  init_auth_state_freshness();
19385
20717
  init_skip_coverage_detector();
19386
20718
  init_feature_coverage();
@@ -19392,11 +20724,14 @@ var init_dist = __esm({
19392
20724
  init_deepgram_voices();
19393
20725
  init_feature_flags();
19394
20726
  init_env_flags();
20727
+ init_visual_compare_limits();
20728
+ init_visual_baseline_identity();
19395
20729
  init_analytics_url();
19396
20730
  init_agentic_cli_runtime();
19397
20731
  init_api_model_catalog();
19398
20732
  init_engine_spec();
19399
20733
  init_provider_chains();
20734
+ init_provider_accounts();
19400
20735
  init_project_engine_overrides();
19401
20736
  init_ai_profiles();
19402
20737
  init_openrouter_usage();
@@ -19437,6 +20772,8 @@ var init_dist = __esm({
19437
20772
  init_promotion_quality_gate();
19438
20773
  init_regex_alternation();
19439
20774
  init_page_evidence();
20775
+ init_trailing_slashes();
20776
+ init_stated_limits();
19440
20777
  }
19441
20778
  });
19442
20779
 
@@ -19465,17 +20802,12 @@ export {
19465
20802
  stepsNeedingReviewForPlatform,
19466
20803
  parseBoolFlag,
19467
20804
  decideHarPosted,
20805
+ deriveAuthPlan,
19468
20806
  isConfigLikeReason,
19469
20807
  classifyErrorMessage,
19470
20808
  isAppOriginResolutionEnabled,
19471
20809
  resolveCrossOriginConfigOverride,
19472
- deriveAuthPlan,
19473
- detectAuthBlock,
19474
- detectApiColdSessionBlock,
19475
- coldSessionConfigMessage,
19476
- detectSkippedApiCoverage,
19477
20810
  isSharedAuthInjectEnabled,
19478
- isAuthReadinessGuardEnabled,
19479
20811
  isRepositoryCli,
19480
20812
  repositoryCliLabel,
19481
20813
  dist_exports,