@validate.qa/runner 1.0.101 → 1.0.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +3 -0
  2. package/dist/{chunk-SN3PN4G4.mjs → chunk-36AWDKPL.mjs} +6 -2
  3. package/dist/{chunk-CYWHPXNZ.mjs → chunk-7SH3COMQ.mjs} +1 -1
  4. package/dist/{chunk-BBTK6OX6.mjs → chunk-BECCGMU4.mjs} +383 -11
  5. package/dist/{chunk-4SZC5ZX3.mjs → chunk-CG75I3ZZ.mjs} +25477 -5374
  6. package/dist/{chunk-TPU42VH2.mjs → chunk-CMM4DOXC.mjs} +10 -5
  7. package/dist/{chunk-M3VZGHZQ.mjs → chunk-NNJYKR3O.mjs} +22 -11
  8. package/dist/{chunk-IGXGSDE3.mjs → chunk-OTKJZIOD.mjs} +1298 -384
  9. package/dist/{chunk-CO5KHIVX.mjs → chunk-TK5KEGNW.mjs} +1 -1
  10. package/dist/{chunk-FQUH2PU2.mjs → chunk-WVLBQDFP.mjs} +140 -7
  11. package/dist/{chunk-2OKZY3WN.mjs → chunk-YI3ZC2XI.mjs} +2 -2
  12. package/dist/{chunk-XZUUBMP3.mjs → chunk-YIM746WO.mjs} +1 -1
  13. package/dist/cli.js +140692 -118903
  14. package/dist/cli.mjs +362 -108
  15. package/dist/credential-mcp-server.js +1402 -350
  16. package/dist/credential-mcp-server.mjs +3 -3
  17. package/dist/discovery-mcp-server.js +1330 -401
  18. package/dist/discovery-mcp-server.mjs +4 -4
  19. package/dist/dist-2G7YTNH5.mjs +17 -0
  20. package/dist/inbox-mcp-server.js +1676 -629
  21. package/dist/inbox-mcp-server.mjs +3 -3
  22. package/dist/route-seed-worker.js +1244 -330
  23. package/dist/route-seed-worker.mjs +4 -4
  24. package/dist/run-write-guard-preload.js +1622 -332
  25. package/dist/run-write-guard-preload.mjs +4 -4
  26. package/dist/run-write-guard.js +1622 -332
  27. package/dist/run-write-guard.mjs +3 -3
  28. package/dist/spec-driver.js +1 -1
  29. package/dist/spec-driver.mjs +1 -1
  30. package/package.json +3 -2
  31. package/dist/dist-VTPBY2C4.mjs +0 -17
@@ -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, TEST_DID_NOT_RUN_ERROR, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
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";
@@ -1848,9 +1874,10 @@ var init_run_classification = __esm({
1848
1874
  ];
1849
1875
  ENVIRONMENTAL_ERROR_STORED_PREFIXES = [
1850
1876
  ...ENVIRONMENTAL_ERROR_PREFIXES.flatMap((prefix) => [prefix, `Error: ${prefix}`]),
1851
- // Written by the cancel routes as is, with no prefix wrapper.
1852
- USER_CANCELLED_RUN_ERROR
1877
+ // Written by the cancels as is, with no prefix wrapper.
1878
+ ...CANCELLED_REASON_PREFIXES
1853
1879
  ];
1880
+ FEATURE_ROW_RUN_WINDOW = 10;
1854
1881
  BUG_SURFACE_SUPPRESSING_TAGS = ["@rate-limit-blocked", "@quota-exceeded", "@auth-lost"];
1855
1882
  QUARANTINE_CLOSE_TAGS = [
1856
1883
  "@bug-found",
@@ -3134,9 +3161,40 @@ function semanticTarget(label, platform, base) {
3134
3161
  }
3135
3162
  };
3136
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
+ }
3137
3196
  function containsForeignAppPackage(value, targetAppId) {
3138
- for (const match of value.matchAll(ANDROID_RESOURCE_OWNER_RE)) {
3139
- const owner = match[1];
3197
+ for (const owner of androidResourceOwners(value)) {
3140
3198
  if (targetAppId && owner !== targetAppId && !owner.startsWith(`${targetAppId}.`))
3141
3199
  return true;
3142
3200
  }
@@ -3273,7 +3331,7 @@ function healMobileTestSteps(input) {
3273
3331
  summary: changed ? uniq(summary).slice(0, 12) : []
3274
3332
  };
3275
3333
  }
3276
- 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;
3277
3335
  var init_mobile_step_healer = __esm({
3278
3336
  "../shared/dist/mobile-step-healer.js"() {
3279
3337
  "use strict";
@@ -3294,7 +3352,7 @@ var init_mobile_step_healer = __esm({
3294
3352
  "assertStateDelta"
3295
3353
  ]);
3296
3354
  FOREIGN_PACKAGE_RE = /\b[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*){2,}\b/gi;
3297
- ANDROID_RESOURCE_OWNER_RE = /([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+):id\//gi;
3355
+ ANDROID_RESOURCE_ID_SUFFIX_RE = /:id\//gi;
3298
3356
  COMMON_EXTERNAL_PREFIXES = [
3299
3357
  "com.android.",
3300
3358
  "com.google.",
@@ -4727,7 +4785,7 @@ var init_assertion_classifier = __esm({
4727
4785
  // toHaveCount(0) stays tier 4 (absence proof) via TIER4_PATTERNS.
4728
4786
  [/\.toHaveCount\s*\(\s*[1-9]\d*\s*\)/, "non-zero toHaveCount asserts a record count that changes between runs"],
4729
4787
  // parenthesised counts like "(3)" or "[12]" in text - but not Playwright method args
4730
- [/(?<!\.\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"],
4731
4789
  // bare email addresses in assertion content
4732
4790
  [/\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b/i, "email address is user-specific"],
4733
4791
  // currency/balance amounts (dynamic account data)
@@ -4973,7 +5031,7 @@ var init_assertion_proof = __esm({
4973
5031
  PRESENCE_MATCHER = /\.(?:toBeVisible|toBeAttached|toBeEnabled|toBeInViewport|toBeEditable|toBeFocused)\s*\(|\.toHaveCount\s*\(\s*1\s*\)/;
4974
5032
  TEXT_MATCHER = /\.(?:toHaveText|toContainText)\s*\(/;
4975
5033
  ANY_VALUE_SOURCE = /^(?:\.|\\S|\\w|\\d|\[\^\\s\]|\[0-9\]|\[\\d\]|\[\\w\]|\[\\s\\S\])[+*]?\??$/;
4976
- 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*[,)]/;
4977
5035
  EMPTY_NOT_VALUE = /\.not\s*\.\s*toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/;
4978
5036
  TEXT_MATCHER_LITERAL = /\.(?:toHaveText|toContainText)\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/;
4979
5037
  GET_BY_TEXT_LITERAL = /getByText\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/;
@@ -4990,7 +5048,7 @@ var init_assertion_proof = __esm({
4990
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;
4991
5049
  TEXT_LOCATOR = /\.getByText\s*\(|\.filter\s*\(\s*\{\s*hasText\s*:/g;
4992
5050
  CSS_LOCATOR = /\.locator\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/g;
4993
- SPECIFIC_CSS = /\[[^\]]*=|#[\w-]|has-text|:text/;
5051
+ SPECIFIC_CSS = /\[[^\][=]*=|#[\w-]|has-text|:text/;
4994
5052
  }
4995
5053
  });
4996
5054
 
@@ -5954,22 +6012,37 @@ function classifyAuthHandoffIntent(text) {
5954
6012
  function withoutIdpUrlAsserts(code) {
5955
6013
  return code.replace(/\.(?:toHaveURL|waitForURL)\s*\((?:[^)(]|\([^)]*\))*\)/g, (match) => /accountchooser|accounts\.google|appleid\.apple|login\.microsoftonline|\/v\d+\/signin|oauth/i.test(match) ? "" : match);
5956
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
+ }
5957
6026
  function stripComments(code) {
5958
- return replaceBlockComments(code, () => "").split("\n").map((line) => line.replace(/\/\/.*$/, "")).join("\n");
6027
+ return replaceBlockComments(code, () => "").split("\n").map(stripLineComment).join("\n");
5959
6028
  }
5960
- function balancedCall(code, start) {
5961
- const open = code.indexOf("(", start);
5962
- if (open < 0)
5963
- return null;
5964
- let depth = 0;
5965
- 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++) {
5966
6033
  const c = code[i];
5967
6034
  if (c === "(")
5968
- depth++;
5969
- else if (c === ")" && --depth === 0)
5970
- 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;
5971
6038
  }
5972
- 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] };
5973
6046
  }
5974
6047
  function resolveLocator(code, subject) {
5975
6048
  const id = subject.trim().match(/^([A-Za-z_$][\w$]*)$/);
@@ -6017,8 +6090,10 @@ function isAuthHandoffDismissCode(code) {
6017
6090
  const expects = [];
6018
6091
  const re = new RegExp(EXPECT_CALL_RE.source, "g");
6019
6092
  let match;
6093
+ let close = null;
6020
6094
  while (match = re.exec(body)) {
6021
- const found = balancedCall(body, match.index);
6095
+ close ??= closingParens(body);
6096
+ const found = balancedCall(body, close, match.index);
6022
6097
  if (found)
6023
6098
  expects.push({ inner: found.inner, tail: body.slice(found.end, found.end + 48) });
6024
6099
  }
@@ -6063,12 +6138,209 @@ var init_auth_handoff_coverage = __esm({
6063
6138
  }
6064
6139
  });
6065
6140
 
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);
6146
+ }
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);
6151
+ }
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
+
6066
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
+ }
6067
6339
  function liveExpectStatements(uncommented) {
6068
6340
  const masked = maskedOutsideStrings(uncommented).split("\n");
6069
6341
  return accumulateStatements(uncommented.split("\n")).filter((s) => EXPECT_STMT.test(s.stmt) && EXPECT_STMT.test(masked[s.startLine - 1] ?? ""));
6070
6342
  }
6071
- function stripLineComment(ln) {
6343
+ function stripLineComment2(ln) {
6072
6344
  let quote = null;
6073
6345
  for (let i = 0; i < ln.length; i++) {
6074
6346
  const c = ln[i];
@@ -6091,7 +6363,7 @@ function splitInteractionStatementsWithLines(lines) {
6091
6363
  let depth = 0;
6092
6364
  let scaffold = false;
6093
6365
  for (let i = 0; i < lines.length; i++) {
6094
- const ln = stripLineComment(lines[i]).trim();
6366
+ const ln = stripLineComment2(lines[i]).trim();
6095
6367
  if (!ln)
6096
6368
  continue;
6097
6369
  const isContinuation = out.length > 0 && (ln.startsWith(".") || depth > 0 && !scaffold);
@@ -6115,7 +6387,7 @@ function testBlockIds(lines) {
6115
6387
  let any = false;
6116
6388
  let block = 0;
6117
6389
  for (let i = 0; i < lines.length; i++) {
6118
- const ln = stripLineComment(lines[i]).trim();
6390
+ const ln = stripLineComment2(lines[i]).trim();
6119
6391
  if (!open) {
6120
6392
  if (!TEST_BLOCK_OPEN.test(ln))
6121
6393
  continue;
@@ -6199,7 +6471,7 @@ function expectSubjectSpan(stmt) {
6199
6471
  }
6200
6472
  return null;
6201
6473
  }
6202
- function resolveSubjectBindings(stmts, bindings, blockOfLine) {
6474
+ function resolveSubjectBindings(stmts, bindings, blockOfLine, boundAt) {
6203
6475
  if (bindings.length === 0)
6204
6476
  return [...stmts];
6205
6477
  return stmts.map((item) => {
@@ -6222,14 +6494,22 @@ function resolveSubjectBindings(stmts, bindings, blockOfLine) {
6222
6494
  subject = `${lead[1]}${found.expr.replace(/;\s*$/, "")}${subject.slice(lead[0].length)}`;
6223
6495
  line = found.line;
6224
6496
  }
6225
- 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;
6226
6501
  });
6227
6502
  }
6228
6503
  function stripLoginScaffolding(code) {
6229
- 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 = [];
6230
6509
  let inAuth = false;
6231
6510
  let inInbox = false;
6232
- for (const ln of code.split("\n")) {
6511
+ for (let i = 0; i < lines.length; i++) {
6512
+ const ln = lines[i];
6233
6513
  if (!inAuth && !inInbox && AUTH_START.test(ln)) {
6234
6514
  inAuth = true;
6235
6515
  continue;
@@ -6250,9 +6530,9 @@ function stripLoginScaffolding(code) {
6250
6530
  }
6251
6531
  if (LOGIN_CALL_LINE.test(ln))
6252
6532
  continue;
6253
- out.push(ln);
6533
+ kept.push(i);
6254
6534
  }
6255
- return out.join("\n");
6535
+ return kept;
6256
6536
  }
6257
6537
  function stripCommentsAndLogs(code) {
6258
6538
  return stripCoverageComments(code).split("\n").filter((ln) => !/^\s*console\./.test(ln)).join("\n");
@@ -6404,6 +6684,41 @@ function isVacuousErrorAbsence(stmt, presence) {
6404
6684
  return false;
6405
6685
  return !presence().includes(m[1]);
6406
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
+ }
6407
6722
  function isBareContainerVisibility(stmt) {
6408
6723
  return BARE_CONTAINER_ROLE.test(stmt) && !GET_BY_TEXT.test(stmt) && !HAS_TEXT_FILTER.test(stmt) && !TEXT_CONTENT_MATCHER.test(stmt);
6409
6724
  }
@@ -6485,7 +6800,7 @@ function roleNames(s) {
6485
6800
  function interactedControlNames(lines) {
6486
6801
  const names = /* @__PURE__ */ new Set();
6487
6802
  for (const raw of lines) {
6488
- const ln = stripLineComment(raw);
6803
+ const ln = stripLineComment2(raw);
6489
6804
  if (!INTERACT_VERB.test(ln))
6490
6805
  continue;
6491
6806
  for (const n of roleNames(ln))
@@ -6681,6 +6996,14 @@ function balancedArguments(code, from, limit = code.length) {
6681
6996
  }
6682
6997
  return null;
6683
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
+ }
6684
7007
  function matcherArgumentsAreConstant(body, from) {
6685
7008
  MATCHER_OPEN.lastIndex = from;
6686
7009
  const opened = MATCHER_OPEN.exec(body);
@@ -6722,9 +7045,12 @@ function envValueKey(name) {
6722
7045
  function enteredValues(stmts) {
6723
7046
  const values = /* @__PURE__ */ new Map();
6724
7047
  const navLines = [];
7048
+ const accountConsts = /* @__PURE__ */ new Set();
6725
7049
  for (const { stmt, startLine } of stmts) {
6726
7050
  if (PAGE_NAV.test(stmt))
6727
7051
  navLines.push(startLine);
7052
+ for (const c of stmt.matchAll(ACCOUNT_CONST))
7053
+ accountConsts.add(c[1].replace(/\$/g, "\\$"));
6728
7054
  let m;
6729
7055
  ENTERED_VALUE.lastIndex = 0;
6730
7056
  const add = (value) => {
@@ -6740,7 +7066,8 @@ function enteredValues(stmts) {
6740
7066
  while (m = ENTERED_ENV.exec(stmt))
6741
7067
  add(envValueKey(m[1] ?? m[2] ?? m[3] ?? m[4]));
6742
7068
  }
6743
- return { values, navLines };
7069
+ const consts = accountConsts.size > 0 ? `|(?<![\\w$.])(?:${[...accountConsts].join("|")})(?![\\w$])` : "";
7070
+ return { values, navLines, account: new RegExp(`${ACCOUNT_ENV}${consts}`) };
6744
7071
  }
6745
7072
  function countBelow(lines, line) {
6746
7073
  let lo = 0;
@@ -6877,6 +7204,9 @@ function comparesWithItself(subject, tail) {
6877
7204
  return expected === squash2(m[1] === "toHaveCount" ? `${subject}.count()` : subject);
6878
7205
  }
6879
7206
  function asPlainAssertion(s) {
7207
+ return plainAssertion(s, false);
7208
+ }
7209
+ function plainAssertion(s, keepSelfCompare) {
6880
7210
  const { stmt } = s;
6881
7211
  if (COUNT_AT_LEAST_ZERO.test(stmt))
6882
7212
  return [];
@@ -6885,13 +7215,13 @@ function asPlainAssertion(s) {
6885
7215
  return [s];
6886
7216
  const plain = subject.replace(/^await\s+/, "").trim();
6887
7217
  const subjectAt = stmt.indexOf(subject, stmt.search(EXPECT_CALL_RE));
6888
- const tail = stmt.slice(subjectAt + subject.length).trimStart();
7218
+ const tail = (stmt.startsWith("expect.poll", stmt.search(EXPECT_CALL_RE)) ? expectCallTail(stmt) : null) ?? stmt.slice(subjectAt + subject.length).trimStart();
6889
7219
  const as = (rewritten) => [{ ...s, stmt: rewritten }];
6890
7220
  if (LOCATOR_EXPR.test(plain) && !LOCATOR_VALUE_READ.test(plain) && ALWAYS_TRUE_ON_OBJECT.test(tail))
6891
7221
  return [];
6892
7222
  if (DEFINED_VALUE_READ.test(plain) && ALWAYS_DEFINED.test(tail))
6893
7223
  return [];
6894
- if (comparesWithItself(plain, tail))
7224
+ if (!keepSelfCompare && comparesWithItself(plain, tail))
6895
7225
  return [];
6896
7226
  const visible = /^(.+)\.\s*isVisible\s*\(\s*\)$/s.exec(plain);
6897
7227
  if (visible && IS_VISIBLE_TRUE.test(tail))
@@ -6902,6 +7232,9 @@ function asPlainAssertion(s) {
6902
7232
  }
6903
7233
  return [s];
6904
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
+ }
6905
7238
  function creditsOutcomeAlways(stmt, entered, startLine) {
6906
7239
  if (!OUTCOME_ALWAYS.some((re) => re.test(stmt)))
6907
7240
  return false;
@@ -6934,6 +7267,22 @@ function successNoticeQuote(stmt) {
6934
7267
  }
6935
7268
  return null;
6936
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
+ }
6937
7286
  function provesStoredControlValue(stmt, startLine, persistence) {
6938
7287
  if (!persistence || startLine === void 0)
6939
7288
  return false;
@@ -6960,16 +7309,80 @@ function reopenedAfterSave(startLine, persistence, reopens = persistence.reloadL
6960
7309
  }
6961
7310
  return lo < reopens.length && reopens[lo] < startLine;
6962
7311
  }
6963
- function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine, persistence) {
6964
- if (mutated && hasAttributeTransition(stmts))
6965
- return { kind: "durable" };
6966
- const notices = [];
6967
- const note = (text) => {
6968
- if (notices.length === 0)
6969
- notices.push(text);
6970
- };
6971
- let presenceText;
6972
- const presence = () => presenceText ??= stmts.filter((o) => !ABSENCE_MATCHER.test(o.stmt) && !NEGATED_ASSERT.test(o.stmt)).map((o) => o.stmt).join("\n");
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) {
7370
+ if (mutated && hasAttributeTransition(stmts))
7371
+ return { kind: "durable" };
7372
+ const notices = [];
7373
+ const note = (text) => {
7374
+ if (notices.length === 0)
7375
+ notices.push(text);
7376
+ };
7377
+ let presenceText;
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
+ };
6973
7386
  for (const { stmt, startLine } of stmts) {
6974
7387
  if (firstMutLine !== void 0 && startLine !== void 0 && startLine < firstMutLine)
6975
7388
  continue;
@@ -6977,6 +7390,11 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
6977
7390
  continue;
6978
7391
  if (COUNT_ONE.test(stmt) && quotedAssertedStrings(stmt).some((t) => clicked.has(t.toLowerCase())))
6979
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
+ }
6980
7398
  if (creditsOutcomeAlways(stmt, entered, startLine))
6981
7399
  return { kind: "durable" };
6982
7400
  if (mutated && !NEGATED_ASSERT.test(stmt) && MESSAGE_SURFACE.test(stmt) && OUTCOME_TEXT.test(assertedText(stmt))) {
@@ -7058,6 +7476,53 @@ function noticeOnlyExemption(clicked, noticeText, interactionStmts) {
7058
7476
  function detectOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine) {
7059
7477
  return classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine).kind !== "none";
7060
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
+ }
7061
7526
  function validateAssertionStrength(code, opts = {}) {
7062
7527
  const body = stripLoginScaffolding(code);
7063
7528
  const lines = body.split("\n");
@@ -7101,7 +7566,13 @@ function validateAssertionStrength(code, opts = {}) {
7101
7566
  const interactionStmts = inlineLocatorBindings(interactionStmtsRaw);
7102
7567
  const blockIds = testBlockIds(lines);
7103
7568
  const blockOfLine = (line) => blockIds?.[line - 1] ?? 0;
7104
- 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
+ });
7105
7576
  const insideTest = linesInsideTestBlocks(lines);
7106
7577
  const inTestOrder = (i) => insideTest === null || insideTest[interactionStmtsWithLines[i].startLine - 1] === true;
7107
7578
  let mutated = opts.interactionPerformed === true;
@@ -7111,9 +7582,34 @@ function validateAssertionStrength(code, opts = {}) {
7111
7582
  let navClicks = 0;
7112
7583
  const persistence = { mutLines: [], reloadLines: [], reloadCalls: [] };
7113
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
+ };
7114
7608
  for (let i = 0; i < interactionStmts.length; i++) {
7115
7609
  const ln = interactionStmts[i];
7116
7610
  const at = interactionStmtsWithLines[i].startLine;
7611
+ if (teardown !== null && acts.last?.kind === "fill" && ENTER_PRESS.test(ln))
7612
+ noteAct(i, at, "enter");
7117
7613
  if (RELOAD.test(ln)) {
7118
7614
  persistence.reloadLines.push(at);
7119
7615
  persistence.reloadCalls.push(at);
@@ -7121,32 +7617,31 @@ function validateAssertionStrength(code, opts = {}) {
7121
7617
  const opened = ln.match(/\.goto\s*\(\s*([^,)]+)/);
7122
7618
  if (opened) {
7123
7619
  const page = opened[1].trim();
7124
- if (openedPages.has(page))
7620
+ const path = namedPagePath(page);
7621
+ if (openedPages.has(page) || path !== null && savedOnCheckedPage.has(path))
7125
7622
  persistence.reloadLines.push(at);
7126
- else
7127
- openedPages.add(page);
7128
- }
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]);
7129
7631
  if (FORM_INPUT.test(ln)) {
7130
7632
  mutated = true;
7131
- lastMutIdx = i;
7132
- if (inTestOrder(i)) {
7133
- persistence.mutLines.push(at);
7134
- if (firstMutIdx < 0)
7135
- firstMutIdx = i;
7136
- }
7633
+ saveAt(i, at);
7634
+ noteAct(i, at, "fill");
7137
7635
  continue;
7138
7636
  }
7139
7637
  if (ANY_CLICK.test(ln)) {
7140
7638
  if (NAV_LOCATOR.test(ln)) {
7141
7639
  navClicks++;
7640
+ checkedPage = null;
7142
7641
  } else {
7143
7642
  mutated = true;
7144
- lastMutIdx = i;
7145
- if (inTestOrder(i)) {
7146
- persistence.mutLines.push(at);
7147
- if (firstMutIdx < 0)
7148
- firstMutIdx = i;
7149
- }
7643
+ saveAt(i, at);
7644
+ noteAct(i, at, "click");
7150
7645
  }
7151
7646
  }
7152
7647
  }
@@ -7179,9 +7674,28 @@ function validateAssertionStrength(code, opts = {}) {
7179
7674
  };
7180
7675
  }
7181
7676
  const orderedPersistence = firstMutLine !== void 0 ? persistence : void 0;
7182
- 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);
7183
7679
  if (proof.kind === "durable") {
7184
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
+ }
7185
7699
  return { ok: true, severity: "none", reason: "proves an outcome (count / diff / value / revealed content)", signals: signals2 };
7186
7700
  }
7187
7701
  if (mutated && proof.kind === "notice") {
@@ -7230,7 +7744,7 @@ function validateAssertionStrength(code, opts = {}) {
7230
7744
  signals
7231
7745
  };
7232
7746
  }
7233
- const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && URL_PROOF.test(s));
7747
+ const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && isUrlProof(s));
7234
7748
  if (urlProofAfter) {
7235
7749
  return {
7236
7750
  ok: false,
@@ -7267,7 +7781,7 @@ function validateAssertionStrength(code, opts = {}) {
7267
7781
  signals
7268
7782
  };
7269
7783
  }
7270
- var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, NEGATED_ASSERT_NOT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, VALUE_ABSENCE, OPTION_ABSENCE, NOT_BEFORE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, NEGATED_TEXT, ASYNC_DESTRUCTURE_OPEN, REQUEST_WORD, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, ENV_READ, ENTERED_ENV, HAVE_VALUE_ENV, IN_FLIGHT_TEXT, LOCATOR_VALUE_READ, LOCATOR_EXPR, ALWAYS_TRUE_ON_OBJECT, COUNT_AT_LEAST_ZERO, DEFINED_VALUE_READ, ALWAYS_DEFINED, SELF_COMPARE_MATCHER, IS_VISIBLE_TRUE, PAGE_VALUE_MATCHER, COUNT_ONE, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD;
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;
7271
7785
  var init_assertion_strength = __esm({
7272
7786
  "../shared/dist/assertion-strength.js"() {
7273
7787
  "use strict";
@@ -7276,6 +7790,7 @@ var init_assertion_strength = __esm({
7276
7790
  init_assertion_proof();
7277
7791
  init_auth_handoff_coverage();
7278
7792
  init_expect_call();
7793
+ init_auth_plan();
7279
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;
7280
7795
  OUTCOME_ALWAYS = [
7281
7796
  /\.toHaveCount\s*\(/,
@@ -7292,7 +7807,7 @@ var init_assertion_strength = __esm({
7292
7807
  // form field value
7293
7808
  /\.toMatch\s*\(/,
7294
7809
  // string/regex content match (e.g. textContent() vs /[2-9] selected/)
7295
- /(?<!\.not\s*)\.\s*toBeDisabled\s*\(/,
7810
+ /\.(?<!\.not\s*\.)\s*toBeDisabled\s*\(/,
7296
7811
  // a control became DISABLED — a gating/validation outcome (not.toBeDisabled is the toBeEnabled twin)
7297
7812
  /\.not\.toBeVisible\s*\(/,
7298
7813
  /\.toBeHidden\s*\(/
@@ -7301,12 +7816,16 @@ var init_assertion_strength = __esm({
7301
7816
  FORM_INPUT = /\.(fill|selectOption|setInputFiles|check|uncheck|setChecked|pressSequentially)\s*\(|keyboard\.type\s*\(/;
7302
7817
  ANY_CLICK = /\.click\s*\(/;
7303
7818
  NAV_LOCATOR = /getByRole\s*\(\s*['"`](?:link|menuitem)['"`]/i;
7304
- URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(|\bexpect\s*\(\s*(?:await\s+)?page\s*\.\s*url\s*\(\s*\)\s*\)|\bexpect\s*\.\s*poll\s*\(\s*(?:async\s*)?\(\s*\)\s*=>\s*(?:\{\s*return\s+)?page\s*\.\s*url\s*\(\s*\)/;
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;
7305
7824
  RELOAD = /\.reload\s*\(/;
7306
7825
  EXPECT_STMT = /\bexpect\s*[.(]/;
7307
7826
  BLOCK_SCAFFOLD2 = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
7308
7827
  TEST_BLOCK_OPEN = /^(?:test|it)(?:\.(?:only|skip|fixme|slow|serial|describe\.serial))?\s*\(|^(?:test\.)?(?:beforeEach|beforeAll)\s*\(/;
7309
- 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*\(.+)$/;
7310
7829
  EXPECT_OPEN = /\bexpect(?:\.soft)?\s*\(/;
7311
7830
  STATE_MATCHER = /\.(toBeChecked|toBeVisible|toHaveValue|toBeDisabled|toBeEnabled)\s*\(/;
7312
7831
  CHROME_LOCATOR = /getByRole\s*\(\s*['"`](?:heading|navigation|banner|main|contentinfo|complementary|region|search|tablist|tab|button|link|menuitem|textbox|searchbox|combobox|spinbutton)['"`]/i;
@@ -7326,7 +7845,7 @@ var init_assertion_strength = __esm({
7326
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;
7327
7846
  AUTH_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_AUTH/;
7328
7847
  AUTH_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_AUTH/;
7329
- 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*)?$/;
7330
7849
  INBOX_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_INBOX/;
7331
7850
  INBOX_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_INBOX/;
7332
7851
  NEGATED_ASSERT = /\.not\s*\.\s*to[A-Z]\w*|\.toBeHidden\s*\(/;
@@ -7336,7 +7855,7 @@ var init_assertion_strength = __esm({
7336
7855
  TITLE_LINE = /^\s*(?:test|describe)(?:\.\w+)?\s*\(/;
7337
7856
  FAILURE_STATUS_ASSERT = [
7338
7857
  /\.status\s*\(\s*\)[^;]{0,200}\.(?:toBeGreaterThanOrEqual|toBeGreaterThan)\s*\(\s*(?:399|[45]\d\d)\b/,
7339
- /\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)/,
7340
7859
  /\bexpect\s*\([^;]{0,200}\.ok\s*\(\s*\)\s*\)\s*\.\s*(?:toBe\s*\(\s*false\s*\)|toBeFalsy\s*\()/
7341
7860
  ];
7342
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;
@@ -7351,6 +7870,7 @@ var init_assertion_strength = __esm({
7351
7870
  NOT_BEFORE_MATCHER = /\.not\s*\.\s*(?=to[A-Z])/g;
7352
7871
  ERROR_TEXT_WORDS = /error|fail|wrong|try again|unable|could ?n.?t|can.?t|problem|invalid|unavailable/i;
7353
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;
7354
7874
  NEGATED_TEXT = /\.not\s*\.\s*(?:toHaveText|toContainText)\s*\(\s*(?:['"`]([^'"`]*)['"`]|\/((?:\\.|[^/\\\n])+)\/)?/;
7355
7875
  ASYNC_DESTRUCTURE_OPEN = /async\s*\(\s*\{/g;
7356
7876
  REQUEST_WORD = /\brequest\b/;
@@ -7377,6 +7897,9 @@ var init_assertion_strength = __esm({
7377
7897
  ENV_READ = String.raw`process\.env(?:(?:\?\.|\.)([A-Za-z_]\w*)|(?:\?\.)?\[\s*['"]([A-Za-z_]\w*)['"]\s*\])`;
7378
7898
  ENTERED_ENV = new RegExp(String.raw`\.(?:fill|pressSequentially)\s*\(\s*${ENV_READ}|keyboard\.type\s*\(\s*${ENV_READ}`, "g");
7379
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;
7381
7904
  LOCATOR_VALUE_READ = /\.(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts|evaluate|evaluateAll|boundingBox|all)\s*\(/;
7382
7905
  LOCATOR_EXPR = /^(?:this\.)?page\s*\.\s*(?:getBy\w+|locator)\s*\(/;
@@ -7388,14 +7911,38 @@ var init_assertion_strength = __esm({
7388
7911
  IS_VISIBLE_TRUE = /^\)*\s*\.\s*(?:toBe\s*\(\s*true\s*\)|toBeTruthy\s*\(\s*\))/;
7389
7912
  PAGE_VALUE_MATCHER = /^\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
7390
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]*)?$/;
7391
7916
  FORM_CONTROL_LOCATOR = /getByRole\s*\(\s*['"`](?:combobox|switch|checkbox|radio|textbox|searchbox|spinbutton|slider)['"`]|getByLabel\s*\(|getByPlaceholder\s*\(/;
7392
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*\(/;
7393
7926
  COPY_CONTROL = /\bcopy\b/i;
7394
7927
  COPIED_NOTICE = /\bcopied\b/i;
7395
7928
  SEND_CONTROL = /\b(send|submit|subscribe|sign up for|contact|feedback|join (the )?waitlist)\b/i;
7396
7929
  SEND_NOTICE = /\b(thank|thanks|sent|subscribed|received|in touch)\b/i;
7397
7930
  MARK_READ_CONTROL = /\bmark(?:ed)? as read\b|\bmark all(?: as)? read\b/i;
7398
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*\(/;
7399
7946
  }
7400
7947
  });
7401
7948
 
@@ -8685,235 +9232,61 @@ function googleBrokerUnavailableLine(lines, testError) {
8685
9232
  else if (GOOGLE_SIGNED_IN.test(text) || text.startsWith("[google-sign-in] "))
8686
9233
  last = null;
8687
9234
  }
8688
- }
8689
- return last;
8690
- }
8691
- function stepConsoleLines(result) {
8692
- const steps = Array.isArray(result) ? result : result && typeof result === "object" && Array.isArray(result.stepResults) ? result.stepResults : [];
8693
- return steps.flatMap((step) => {
8694
- const logs = step && typeof step === "object" ? step.consoleLogs : null;
8695
- return Array.isArray(logs) ? logs.filter((line) => typeof line === "string") : [];
8696
- });
8697
- }
8698
- var GOOGLE_BROKER_UNAVAILABLE, GOOGLE_BROKER_ASKED, GOOGLE_SIGNED_IN, TIMEOUT_ERROR;
8699
- var init_google_broker_lines = __esm({
8700
- "../shared/dist/google-broker-lines.js"() {
8701
- "use strict";
8702
- GOOGLE_BROKER_UNAVAILABLE = /^\[google-sign-in\] (?:(?:busy|rate_limited|timeout|offline): |failed: Google sign-in service (?:answered 50[234]\b|unreachable))/;
8703
- GOOGLE_BROKER_ASKED = /^\[google-sign-in\] the app started Google sign-in; asking the broker/;
8704
- GOOGLE_SIGNED_IN = /^\[google-sign-in\] returned to the app with Google sign-in/;
8705
- TIMEOUT_ERROR = /Test timeout of \d+ms exceeded|TimeoutError|Timeout:? \d+ ?ms/i;
8706
- }
8707
- });
8708
-
8709
- // ../shared/dist/admin-view-return-to.js
8710
- function sanitizeAdminViewReturnTo(value) {
8711
- if (typeof value !== "string")
8712
- return ADMIN_VIEW_DEFAULT_RETURN_TO;
8713
- const trimmed = value.trim();
8714
- if (!trimmed || trimmed.length > MAX_RETURN_TO_LENGTH)
8715
- return ADMIN_VIEW_DEFAULT_RETURN_TO;
8716
- if (!trimmed.startsWith("/") || trimmed.startsWith("//"))
8717
- return ADMIN_VIEW_DEFAULT_RETURN_TO;
8718
- if (trimmed.includes("\\") || /[\s\u0000-\u001f\u007f]/.test(trimmed))
8719
- return ADMIN_VIEW_DEFAULT_RETURN_TO;
8720
- let url;
8721
- try {
8722
- url = new URL(trimmed, PLACEHOLDER_ORIGIN);
8723
- } catch {
8724
- return ADMIN_VIEW_DEFAULT_RETURN_TO;
8725
- }
8726
- if (url.origin !== PLACEHOLDER_ORIGIN)
8727
- return ADMIN_VIEW_DEFAULT_RETURN_TO;
8728
- if (BLOCKED_PATH_RE.test(url.pathname))
8729
- return ADMIN_VIEW_DEFAULT_RETURN_TO;
8730
- const out = `${url.pathname}${url.search}${url.hash}`;
8731
- if (out.startsWith("//") || out.includes("\\"))
8732
- return ADMIN_VIEW_DEFAULT_RETURN_TO;
8733
- return out;
8734
- }
8735
- var ADMIN_VIEW_DEFAULT_RETURN_TO, MAX_RETURN_TO_LENGTH, PLACEHOLDER_ORIGIN, BLOCKED_PATH_RE;
8736
- var init_admin_view_return_to = __esm({
8737
- "../shared/dist/admin-view-return-to.js"() {
8738
- "use strict";
8739
- ADMIN_VIEW_DEFAULT_RETURN_TO = "/sessions";
8740
- MAX_RETURN_TO_LENGTH = 2048;
8741
- PLACEHOLDER_ORIGIN = "https://admin-view.invalid";
8742
- BLOCKED_PATH_RE = /^\/(?:login|admin-view)/i;
8743
- }
8744
- });
8745
-
8746
- // ../shared/dist/auth-plan.js
8747
- function hasInlineLoginSteps(playwrightCode) {
8748
- if (!playwrightCode)
8749
- return false;
8750
- return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
8751
- }
8752
- function hasGeneratedAuthGuard(playwrightCode) {
8753
- if (!playwrightCode)
8754
- return false;
8755
- return /\/\/ >>> VALIDATE_QA_AUTH v(\d+)\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\1\b/.test(playwrightCode);
8756
- }
8757
- function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
8758
- if (!sharedAuthInjectEnabled)
8759
- return false;
8760
- if (tags.includes("@auth"))
8761
- return false;
8762
- if (tags.includes("@api"))
8763
- return true;
8764
- if (!tags.includes("@needs-auth"))
8765
- return false;
8766
- if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
8767
- return true;
8768
- const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
8769
- const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
8770
- return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
8771
- }
8772
- function codeReadsStoredCredentials(code) {
8773
- if (!code)
8774
- return false;
8775
- if (/__vqaLogin\b/.test(code))
8776
- return true;
8777
- for (const m of code.matchAll(/TEST_[A-Z0-9_]+/g)) {
8778
- if (/_(?:EMAIL|PASSWORD)/.test(m[0]))
8779
- return true;
8780
- }
8781
- for (const m of code.matchAll(/process\.env\s*\[([^\]]*)\]?/g)) {
8782
- if (!/^\s*(['"])[^'"`]*\1\s*$/.test(m[1]))
8783
- return true;
8784
- }
8785
- return false;
8786
- }
8787
- function explicitAuthTypeTags(t) {
8788
- const found = /* @__PURE__ */ new Set();
8789
- for (const tag of t) {
8790
- if (AUTH_TYPE_TAGS.includes(tag))
8791
- found.add(tag);
8792
- else if (Object.hasOwn(AUTH_TAG_ALIASES, tag))
8793
- found.add(AUTH_TAG_ALIASES[tag]);
8794
- }
8795
- return [...found];
8796
- }
8797
- function invalidPlan(configurationError) {
8798
- return {
8799
- version: 1,
8800
- source: "invalid",
8801
- acquisition: "unsupported",
8802
- presentation: "none",
8803
- needsCookieState: false,
8804
- needsStandingHeaders: false,
8805
- standingHeadersMode: "none",
8806
- needsLoginCreds: false,
8807
- requiredEnvKeys: [],
8808
- configurationError
8809
- };
8810
- }
8811
- function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
8812
- return {
8813
- version: 1,
8814
- source: "explicit",
8815
- authTypeTag: authTypeTag2,
8816
- acquisition,
8817
- presentation,
8818
- needsCookieState: opts.cookie,
8819
- needsStandingHeaders: opts.standingMode === "required",
8820
- standingHeadersMode: opts.standingMode,
8821
- needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
8822
- requiredEnvKeys: opts.requiredEnvKeys
8823
- };
8824
- }
8825
- function deriveAuthPlan(tags, code, opts) {
8826
- const t = Array.isArray(tags) ? tags : [];
8827
- const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
8828
- const isApi = t.includes("@api");
8829
- const explicit = isApi ? explicitAuthTypeTags(t) : [];
8830
- if (explicit.length > 0) {
8831
- if (t.includes("@auth")) {
8832
- return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
8833
- }
8834
- const loginCompose = explicit.filter((e) => e === AUTH_TAG_COOKIE_STATE || e === AUTH_TAG_LOGIN_COOKIE || e === AUTH_TAG_LOGIN_BEARER);
8835
- if (explicit.length === 2 && explicit.includes(AUTH_TAG_STANDING) && loginCompose.length === 1) {
8836
- const composedKeys = [...TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY];
8837
- switch (loginCompose[0]) {
8838
- case AUTH_TAG_COOKIE_STATE:
8839
- return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "required", requiredEnvKeys: composedKeys });
8840
- case AUTH_TAG_LOGIN_COOKIE:
8841
- return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
8842
- case AUTH_TAG_LOGIN_BEARER:
8843
- return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
8844
- }
8845
- }
8846
- if (explicit.length > 1) {
8847
- return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
8848
- }
8849
- switch (explicit[0]) {
8850
- case AUTH_TAG_UNSUPPORTED:
8851
- return {
8852
- ...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
8853
- };
8854
- case AUTH_TAG_NONE:
8855
- return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
8856
- case AUTH_TAG_STANDING:
8857
- return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
8858
- case AUTH_TAG_LOGIN_BEARER:
8859
- return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
8860
- case AUTH_TAG_LOGIN_COOKIE:
8861
- return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
8862
- case AUTH_TAG_COOKIE_STATE:
8863
- return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
8864
- }
8865
- }
8866
- const cookie = legacyNeedsCookieState(t, code, sharedFlag);
8867
- const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
8868
- const selfLoginWithoutStoredCreds = t.includes("@auth") && !t.includes("@needs-auth") && !isApi && !!code?.trim() && !/TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin\b/.test(code);
8869
- const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
8870
- const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
8871
- return {
8872
- version: 1,
8873
- source: "legacy",
8874
- acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
8875
- presentation: authedLegacy ? "cookie" : "none",
8876
- needsCookieState: cookie,
8877
- needsStandingHeaders: false,
8878
- // A legacy bare-@api test keeps injecting standing headers IF the project configured them
8879
- // (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
8880
- standingHeadersMode: isApi ? "legacy-if-configured" : "none",
8881
- needsLoginCreds,
8882
- requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
8883
- };
9235
+ }
9236
+ return last;
8884
9237
  }
8885
- function authTypeTag(tags) {
8886
- const t = Array.isArray(tags) ? tags : [];
8887
- const explicit = explicitAuthTypeTags(t);
8888
- return explicit.length === 1 ? explicit[0] : void 0;
9238
+ function stepConsoleLines(result) {
9239
+ const steps = Array.isArray(result) ? result : result && typeof result === "object" && Array.isArray(result.stepResults) ? result.stepResults : [];
9240
+ return steps.flatMap((step) => {
9241
+ const logs = step && typeof step === "object" ? step.consoleLogs : null;
9242
+ return Array.isArray(logs) ? logs.filter((line) => typeof line === "string") : [];
9243
+ });
8889
9244
  }
8890
- 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;
8891
- var init_auth_plan = __esm({
8892
- "../shared/dist/auth-plan.js"() {
9245
+ var GOOGLE_BROKER_UNAVAILABLE, GOOGLE_BROKER_ASKED, GOOGLE_SIGNED_IN, TIMEOUT_ERROR;
9246
+ var init_google_broker_lines = __esm({
9247
+ "../shared/dist/google-broker-lines.js"() {
8893
9248
  "use strict";
8894
- AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
8895
- AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
8896
- AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
8897
- AUTH_TAG_STANDING = "@auth-standing";
8898
- AUTH_TAG_NONE = "@auth-none";
8899
- AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
8900
- AUTH_TYPE_TAGS = [
8901
- AUTH_TAG_COOKIE_STATE,
8902
- AUTH_TAG_LOGIN_COOKIE,
8903
- AUTH_TAG_LOGIN_BEARER,
8904
- AUTH_TAG_STANDING,
8905
- AUTH_TAG_NONE,
8906
- AUTH_TAG_UNSUPPORTED
8907
- ];
8908
- AUTH_TAG_ALIASES = {
8909
- "@auth-cookie": AUTH_TAG_COOKIE_STATE,
8910
- "@auth-bearer": AUTH_TAG_LOGIN_BEARER,
8911
- "@auth-token": AUTH_TAG_LOGIN_BEARER,
8912
- "@auth-api-key": AUTH_TAG_STANDING,
8913
- "@auth-standing-header": AUTH_TAG_STANDING
8914
- };
8915
- TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
8916
- API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
9249
+ GOOGLE_BROKER_UNAVAILABLE = /^\[google-sign-in\] (?:(?:busy|rate_limited|timeout|offline): |failed: Google sign-in service (?:answered 50[234]\b|unreachable))/;
9250
+ GOOGLE_BROKER_ASKED = /^\[google-sign-in\] the app started Google sign-in; asking the broker/;
9251
+ GOOGLE_SIGNED_IN = /^\[google-sign-in\] returned to the app with Google sign-in/;
9252
+ TIMEOUT_ERROR = /Test timeout of \d+ms exceeded|TimeoutError|Timeout:? \d+ ?ms/i;
9253
+ }
9254
+ });
9255
+
9256
+ // ../shared/dist/admin-view-return-to.js
9257
+ function sanitizeAdminViewReturnTo(value) {
9258
+ if (typeof value !== "string")
9259
+ return ADMIN_VIEW_DEFAULT_RETURN_TO;
9260
+ const trimmed = value.trim();
9261
+ if (!trimmed || trimmed.length > MAX_RETURN_TO_LENGTH)
9262
+ return ADMIN_VIEW_DEFAULT_RETURN_TO;
9263
+ if (!trimmed.startsWith("/") || trimmed.startsWith("//"))
9264
+ return ADMIN_VIEW_DEFAULT_RETURN_TO;
9265
+ if (trimmed.includes("\\") || /[\s\u0000-\u001f\u007f]/.test(trimmed))
9266
+ return ADMIN_VIEW_DEFAULT_RETURN_TO;
9267
+ let url;
9268
+ try {
9269
+ url = new URL(trimmed, PLACEHOLDER_ORIGIN);
9270
+ } catch {
9271
+ return ADMIN_VIEW_DEFAULT_RETURN_TO;
9272
+ }
9273
+ if (url.origin !== PLACEHOLDER_ORIGIN)
9274
+ return ADMIN_VIEW_DEFAULT_RETURN_TO;
9275
+ if (BLOCKED_PATH_RE.test(url.pathname))
9276
+ return ADMIN_VIEW_DEFAULT_RETURN_TO;
9277
+ const out = `${url.pathname}${url.search}${url.hash}`;
9278
+ if (out.startsWith("//") || out.includes("\\"))
9279
+ return ADMIN_VIEW_DEFAULT_RETURN_TO;
9280
+ return out;
9281
+ }
9282
+ var ADMIN_VIEW_DEFAULT_RETURN_TO, MAX_RETURN_TO_LENGTH, PLACEHOLDER_ORIGIN, BLOCKED_PATH_RE;
9283
+ var init_admin_view_return_to = __esm({
9284
+ "../shared/dist/admin-view-return-to.js"() {
9285
+ "use strict";
9286
+ ADMIN_VIEW_DEFAULT_RETURN_TO = "/sessions";
9287
+ MAX_RETURN_TO_LENGTH = 2048;
9288
+ PLACEHOLDER_ORIGIN = "https://admin-view.invalid";
9289
+ BLOCKED_PATH_RE = /^\/(?:login|admin-view)/i;
8917
9290
  }
8918
9291
  });
8919
9292
 
@@ -8924,13 +9297,16 @@ function stripEchoLines(text) {
8924
9297
  function matchesAny(text, patterns) {
8925
9298
  return patterns.some((p) => p.test(text));
8926
9299
  }
9300
+ function isIntentionalNegativeAuthTest(input) {
9301
+ const meta = [input.testName, input.description].filter(Boolean).join(" ");
9302
+ return matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH);
9303
+ }
8927
9304
  function detectAuthBlock(input) {
8928
9305
  const tags = input.tags ?? [];
8929
9306
  const isAuthed = tags.some((t) => AUTHED_TAGS.includes(t));
8930
9307
  if (!isAuthed)
8931
9308
  return NO_DETECTION;
8932
- const meta = [input.testName, input.description].filter(Boolean).join(" ");
8933
- if (matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH))
9309
+ if (isIntentionalNegativeAuthTest(input))
8934
9310
  return NO_DETECTION;
8935
9311
  const sources = [];
8936
9312
  if (input.error)
@@ -9033,7 +9409,7 @@ var init_auth_block_detector = __esm({
9033
9409
  /\b(reject|deny|denied|block(ed)?)\b[^\n]{0,20}\b(login|sign[\s-]?in|auth|credential|password)\b/i
9034
9410
  ];
9035
9411
  ECHO_LINE_RE = /^\s*(?:(?:📋|🎭|🌐|⚙️?)|(?:stderr:\s*|stdout:\s*)?Locator:)/u;
9036
- 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;
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;
9037
9413
  }
9038
9414
  });
9039
9415
 
@@ -9974,12 +10350,70 @@ function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
9974
10350
  function isAuthReadinessGuardEnabled(value = process.env.AUTH_READINESS_GUARD) {
9975
10351
  return value?.trim().toLowerCase() !== "false";
9976
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
+ }
9977
10359
  var init_feature_flags = __esm({
9978
10360
  "../shared/dist/feature-flags.js"() {
9979
10361
  "use strict";
9980
10362
  }
9981
10363
  });
9982
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
+
9983
10417
  // ../shared/dist/analytics-url.js
9984
10418
  function isSensitiveQueryKey(key) {
9985
10419
  return SENSITIVE_QUERY_KEY_RE.test(key);
@@ -10333,14 +10767,9 @@ function isOpenCodeGoModelId(id) {
10333
10767
  function hasOpenCodeGoPrefix(id) {
10334
10768
  return (id?.trim() ?? "").startsWith(OPENCODE_GO_PREFIX);
10335
10769
  }
10336
- function hasOpenCodeGo2Prefix(id) {
10337
- return (id?.trim() ?? "").startsWith(OPENCODE_GO_2_PREFIX);
10338
- }
10339
10770
  function openCodeGoWireModel(id) {
10340
10771
  const trimmed = id?.trim() ?? "";
10341
- if (!isOpenCodeGoModelId(trimmed))
10342
- return null;
10343
- return trimmed.slice((hasOpenCodeGo2Prefix(trimmed) ? OPENCODE_GO_2_PREFIX : OPENCODE_GO_PREFIX).length);
10772
+ return isOpenCodeGoModelId(trimmed) ? trimmed.slice(OPENCODE_GO_PREFIX.length) : null;
10344
10773
  }
10345
10774
  function isZaiModelId(id) {
10346
10775
  const trimmed = id?.trim() ?? "";
@@ -10449,7 +10878,7 @@ function museCliContributorMode(env = process.env) {
10449
10878
  const raw = (env.MUSE_CLI_CONTRIBUTOR_MODE ?? "").trim().toLowerCase();
10450
10879
  return raw === "off" || raw === "false" || raw === "0" || raw === "no" ? "off" : "on";
10451
10880
  }
10452
- var CLI_ENGINE_MODEL_IDS, BROWSER_CLI_ENGINE_IDS, PIPELINE_STEPS, CLI_ENGINE_RULES, ENGINE_NAMES, OPENROUTER_PREFIX, OPENROUTER_MODEL_PATTERN, OPENCODE_GO_PREFIX, OPENCODE_GO_2_PREFIX, OPENCODE_GO_MODEL_PATTERN, ZAI_PREFIX, ZAI_MODEL_PATTERN;
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;
10453
10882
  var init_engine_spec = __esm({
10454
10883
  "../shared/dist/engine-spec.js"() {
10455
10884
  "use strict";
@@ -10508,8 +10937,7 @@ var init_engine_spec = __esm({
10508
10937
  OPENROUTER_PREFIX = "openrouter:";
10509
10938
  OPENROUTER_MODEL_PATTERN = /^openrouter:[a-z0-9][a-z0-9._-]{0,40}\/[A-Za-z0-9._:-]{1,100}$/;
10510
10939
  OPENCODE_GO_PREFIX = "opencode-go:";
10511
- OPENCODE_GO_2_PREFIX = "opencode-go-2:";
10512
- OPENCODE_GO_MODEL_PATTERN = /^opencode-go(?:-2)?:[a-z0-9][a-z0-9._-]{0,100}$/;
10940
+ OPENCODE_GO_MODEL_PATTERN = /^opencode-go:[a-z0-9][a-z0-9._-]{0,100}$/;
10513
10941
  ZAI_PREFIX = "zai:";
10514
10942
  ZAI_MODEL_PATTERN = /^zai:glm-[a-z0-9][a-z0-9._-]{0,60}$/;
10515
10943
  }
@@ -10834,14 +11262,19 @@ function has(map, key) {
10834
11262
  }
10835
11263
  function providerChainFor(modelId) {
10836
11264
  const key = chainKey(modelId);
11265
+ if (key && has(_overrideHops, key))
11266
+ return _overrideHops[key];
10837
11267
  return key && has(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
10838
11268
  }
10839
11269
  function providerChainLeadsFor(modelId) {
10840
11270
  const key = chainKey(modelId);
11271
+ if (key && has(_overrideLeads, key))
11272
+ return _overrideLeads[key];
10841
11273
  return key && has(PROVIDER_CHAIN_LEADS, key) ? PROVIDER_CHAIN_LEADS[key] : [];
10842
11274
  }
10843
11275
  function allChains() {
10844
- return [...Object.entries(PROVIDER_CHAINS), ...Object.entries(PROVIDER_CHAIN_LEADS)];
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)];
10845
11278
  }
10846
11279
  function isProviderChainHopId(modelId) {
10847
11280
  const id = modelId ?? "";
@@ -10858,14 +11291,24 @@ function providerChainHostOf(value, modelValue) {
10858
11291
  return "own";
10859
11292
  if (hasZaiPrefix(value))
10860
11293
  return "zai";
10861
- if (hasOpenCodeGo2Prefix(value))
10862
- return "opencode-go-2";
10863
11294
  if (hasOpenCodeGoPrefix(value))
10864
11295
  return "opencode-go";
10865
11296
  if (hasOpenRouterPrefix(value))
10866
11297
  return "openrouter";
10867
11298
  return "own";
10868
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
+ }
10869
11312
  function providerHostPlan(modelId) {
10870
11313
  const key = chainKey(modelId);
10871
11314
  if (!key)
@@ -10883,7 +11326,140 @@ function providerHostPlan(modelId) {
10883
11326
  }
10884
11327
  return steps;
10885
11328
  }
10886
- var PROVIDER_CHAINS, PROVIDER_CHAIN_LEADS, PROVIDER_HOST_NOT_OFFERED;
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;
11443
+ }
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;
10887
11463
  var init_provider_chains = __esm({
10888
11464
  "../shared/dist/provider-chains.js"() {
10889
11465
  "use strict";
@@ -10895,18 +11471,120 @@ var init_provider_chains = __esm({
10895
11471
  "muse-spark-1.3-contributor": ["openrouter:meta/muse-spark-1.3-contributor"],
10896
11472
  "claude-haiku-5-5": ["opencode-go:claude-haiku-5-5", "openrouter:anthropic/claude-haiku-5.5"],
10897
11473
  "gemini-3.1-pro-preview": ["openrouter:google/gemini-3.1-pro-preview"],
10898
- "deepseek-flash": ["opencode-go-2:deepseek-v4.1-flash", "opencode-go:deepseek-v4.1-flash", "openrouter:deepseek/deepseek-v4.1-flash"],
10899
- "deepseek-v4-pro": ["opencode-go-2:deepseek-v4-pro", "opencode-go:deepseek-v4-pro", "openrouter:deepseek/deepseek-v4-pro"]
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"]
10900
11476
  };
10901
11477
  PROVIDER_CHAIN_LEADS = {
10902
- "openrouter:z-ai/glm-5.3-flash": ["zai:glm-5.3-flash", "opencode-go-2:glm-5.3-flash", "opencode-go:glm-5.3-flash"],
10903
- "openrouter:z-ai/glm-5.3": ["zai:glm-5.3", "opencode-go-2:glm-5.3", "opencode-go:glm-5.3"],
10904
- "openrouter:z-ai/glm-5.2": ["zai:glm-5.2", "opencode-go-2:glm-5.2", "opencode-go:glm-5.2"]
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"]
10905
11481
  };
10906
11482
  PROVIDER_HOST_NOT_OFFERED = {
10907
11483
  "muse-spark-1.3-contributor": { "opencode-go": "OpenCode Go serves Muse only on its Responses API" },
10908
11484
  "gemini-3.1-pro-preview": { "opencode-go": "OpenCode Go does not serve Gemini" }
10909
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:"];
10910
11588
  }
10911
11589
  });
10912
11590
 
@@ -11025,6 +11703,25 @@ var init_project_engine_overrides = __esm({
11025
11703
  }
11026
11704
  });
11027
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
+
11028
11725
  // ../shared/dist/gemini-visual.js
11029
11726
  function visualProDailyBudget(raw) {
11030
11727
  const parsed = raw && /^\s*\d{1,6}\s*$/.test(raw) ? Number(raw.trim()) : NaN;
@@ -11038,7 +11735,7 @@ function isRetiredGeminiModel(id) {
11038
11735
  }
11039
11736
  function geminiNativeModelsUrl(base = process.env.GEMINI_API_BASE) {
11040
11737
  const raw = base && base.trim() || GEMINI_OPENAI_BASE;
11041
- const native = raw.replace(/\/openai\/?$/, "/").replace(/\/+$/, "");
11738
+ const native = stripTrailingSlashes(raw.replace(/\/openai\/?$/, "/"));
11042
11739
  return `${native}/models?pageSize=1000`;
11043
11740
  }
11044
11741
  function asRecord(value) {
@@ -11158,6 +11855,7 @@ var KNOWN_INPUT_USD_PER_M, GEMINI_OPENAI_BASE, DEFAULT_GEMINI_SCREEN_MODEL, DEFA
11158
11855
  var init_gemini_visual = __esm({
11159
11856
  "../shared/dist/gemini-visual.js"() {
11160
11857
  "use strict";
11858
+ init_trailing_slashes();
11161
11859
  KNOWN_INPUT_USD_PER_M = {
11162
11860
  "gemini-2.5-flash-lite": 0.1,
11163
11861
  "gemini-3.1-flash-lite": 0.25,
@@ -12500,12 +13198,13 @@ function truncateAtWord(text, max) {
12500
13198
  const slice = trimmed.slice(0, room + 1);
12501
13199
  const lastSpace = slice.lastIndexOf(" ");
12502
13200
  const cut = lastSpace > room * 0.5 ? slice.slice(0, lastSpace) : trimmed.slice(0, room);
12503
- return `${cut.replace(/[\s,;:.\-–—]+$/u, "")}\u2026`;
13201
+ return `${stripTrailingChars(cut, TRAILING_PUNCTUATION)}\u2026`;
12504
13202
  }
12505
- 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;
12506
13204
  var init_canvas_support = __esm({
12507
13205
  "../shared/dist/canvas-support.js"() {
12508
13206
  "use strict";
13207
+ init_trailing_slashes();
12509
13208
  CANVAS_UNSUPPORTED_CODE = "canvas_unsupported";
12510
13209
  CANVAS_UNSUPPORTED_TITLE = "Games and canvas apps aren't supported yet";
12511
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.";
@@ -12630,6 +13329,7 @@ var init_canvas_support = __esm({
12630
13329
  TOUCH_NONE_RE = /touch-action\s{0,8}:\s{0,8}none/;
12631
13330
  CODE_TAGS = /* @__PURE__ */ new Set(["script", "link", "flt-glass-pane", "flutter-view", "flt-scene-host"]);
12632
13331
  STATIC_STANDARD_MIN_INTERACTIVE = 3;
13332
+ TRAILING_PUNCTUATION = /[\s,;:.\-–—]/u;
12633
13333
  }
12634
13334
  });
12635
13335
 
@@ -12812,22 +13512,22 @@ function joinChainedLines(code) {
12812
13512
  const trimmed = lines[i].trimStart();
12813
13513
  if (last !== -1 && trimmed.startsWith(".") && !trimmed.startsWith("...")) {
12814
13514
  if (!lastStripped) {
12815
- lines[last] = stripLineComment2(lines[last]).trimEnd();
13515
+ lines[last] = stripLineComment3(lines[last]).trimEnd();
12816
13516
  lastStripped = true;
12817
13517
  }
12818
13518
  if (lines[last].length <= MAX_LINE_CHARS)
12819
- lines[last] = `${lines[last]} ${stripLineComment2(trimmed).trimEnd()}`;
13519
+ lines[last] = `${lines[last]} ${stripLineComment3(trimmed).trimEnd()}`;
12820
13520
  lines[i] = "";
12821
13521
  continue;
12822
13522
  }
12823
- if (stripLineComment2(lines[i]).trim()) {
13523
+ if (stripLineComment3(lines[i]).trim()) {
12824
13524
  last = i;
12825
13525
  lastStripped = false;
12826
13526
  }
12827
13527
  }
12828
13528
  return lines.join("\n");
12829
13529
  }
12830
- function stripLineComment2(rawLine) {
13530
+ function stripLineComment3(rawLine) {
12831
13531
  if (rawLine.trimStart().startsWith("//"))
12832
13532
  return "";
12833
13533
  const at = rawLine.indexOf(" // ");
@@ -12876,7 +13576,7 @@ function analyzeReadinessPolling(rawCode) {
12876
13576
  offset += rawLine.length + 1;
12877
13577
  if (rawLine.length > MAX_LINE_CHARS)
12878
13578
  continue;
12879
- const line = stripLineComment2(rawLine);
13579
+ const line = stripLineComment3(rawLine);
12880
13580
  if (!line.trim())
12881
13581
  continue;
12882
13582
  const literals = literalsOf(line);
@@ -12988,7 +13688,7 @@ function blankBlockComments2(code) {
12988
13688
  from = close + 2;
12989
13689
  }
12990
13690
  }
12991
- function stripLineComment3(rawLine) {
13691
+ function stripLineComment4(rawLine) {
12992
13692
  if (rawLine.trimStart().startsWith("//"))
12993
13693
  return "";
12994
13694
  const at = rawLine.indexOf(" // ");
@@ -12999,7 +13699,7 @@ function logicalLines(code) {
12999
13699
  for (const raw of code.split("\n")) {
13000
13700
  if (raw.length > MAX_LINE_CHARS2)
13001
13701
  continue;
13002
- const line = stripLineComment3(raw).trimEnd();
13702
+ const line = stripLineComment4(raw).trimEnd();
13003
13703
  if (!line.trim())
13004
13704
  continue;
13005
13705
  const trimmed = line.trimStart();
@@ -13361,8 +14061,19 @@ function short4(text, max) {
13361
14061
  const flat = text.replace(/\s+/g, " ").trim();
13362
14062
  return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
13363
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
+ }
13364
14075
  function digitsOf(token) {
13365
- return token.replace(/[^\d.]/g, "").replace(/\.0+$|(\.\d*?)0+$/, "$1").replace(/^0+(?=\d)/, "");
14076
+ return stripFractionZeros(token.replace(/[^\d.]/g, "")).replace(/^0+(?=\d)/, "");
13366
14077
  }
13367
14078
  function computes(line) {
13368
14079
  const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX").replace(LITERAL_ARITH_RE, "N");
@@ -13580,7 +14291,7 @@ function findExpectCalls(code) {
13580
14291
  if (!subj)
13581
14292
  continue;
13582
14293
  const rest = code.slice(subj.end + 1, subj.end + 1 + 80);
13583
- 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);
13584
14295
  if (!mm)
13585
14296
  continue;
13586
14297
  const argStart = subj.end + 1 + mm[0].length;
@@ -15530,6 +16241,28 @@ function startsWithRegexLiteral(text) {
15530
16241
  const t = text.trimStart();
15531
16242
  return t[0] === "/" && t[1] !== "/" && t[1] !== "*" && regexLiteralEnd4(t, 0) !== -1;
15532
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
+ }
15533
16266
  function afterFirstArg(args) {
15534
16267
  let depth = 0;
15535
16268
  let quote = null;
@@ -15567,6 +16300,7 @@ function analyzeRegexMatchers(rawCode) {
15567
16300
  let examined = 0;
15568
16301
  let line = 1;
15569
16302
  let lineAt2 = 0;
16303
+ let regexNames;
15570
16304
  while ((m = re.exec(code)) !== null && examined++ < MAX_CALLS && out.length < MAX_REGEX_MATCHER_FINDINGS) {
15571
16305
  const subj = readBalanced(code, m.index + m[0].length);
15572
16306
  if (!subj)
@@ -15578,10 +16312,18 @@ function analyzeRegexMatchers(rawCode) {
15578
16312
  if (!args)
15579
16313
  continue;
15580
16314
  const expected = mm[2] === "toHaveJSProperty" ? afterFirstArg(args.text) : args.text;
15581
- if (!startsWithRegexLiteral(expected))
15582
- continue;
15583
- if (mm[2] !== "toHaveJSProperty" && mm[2] !== "toContain" && (startsWithRegexLiteral(subj.text) || /^new\s+RegExp\b/.test(subj.text.trim())))
16315
+ if (!isBareRegexLiteral(expected))
15584
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
+ }
15585
16327
  for (let i = code.indexOf("\n", lineAt2); i !== -1 && i < m.index; i = code.indexOf("\n", i + 1)) {
15586
16328
  line++;
15587
16329
  lineAt2 = i + 1;
@@ -15591,9 +16333,12 @@ function analyzeRegexMatchers(rawCode) {
15591
16333
  return out;
15592
16334
  }
15593
16335
  function formatRegexMatcherNote(findings) {
15594
- const lines = findings.map((f) => `Line ${f.line}: .${f.negated ? "not." : ""}${f.matcher}(\u2026 /regex/ \u2026) ${f.negated ? "always passes (a regex never equals the value), so it checks nothing" : "can never pass: this matcher compares by equality and a regex never equals a value"}.`);
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
+ });
15595
16340
  return `${lines.join("\n")}
15596
- A regex only works in toMatch, toHaveText, toContainText, toHaveValue and similar text matchers. For a DOM property read it first: expect(await loc.evaluate((n) => n.<prop>)).toMatch(/re/). Or give toHaveJSProperty the exact string (or boolean) the page shows. Keep every other line as-is.`;
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.`;
15597
16342
  }
15598
16343
  function lintTestQuality(rawCode, opts = {}) {
15599
16344
  if (!rawCode || typeof rawCode !== "string")
@@ -16342,19 +17087,6 @@ var init_fence_runs = __esm({
16342
17087
  }
16343
17088
  });
16344
17089
 
16345
- // ../shared/dist/trailing-slashes.js
16346
- function stripTrailingSlashes(value) {
16347
- let end = value.length;
16348
- while (end > 0 && value.charCodeAt(end - 1) === 47)
16349
- end--;
16350
- return end === value.length ? value : value.slice(0, end);
16351
- }
16352
- var init_trailing_slashes = __esm({
16353
- "../shared/dist/trailing-slashes.js"() {
16354
- "use strict";
16355
- }
16356
- });
16357
-
16358
17090
  // ../shared/dist/survey-bug-signature.js
16359
17091
  function normalizeWords(text) {
16360
17092
  return text.toLowerCase().replace(UUID, " id ").replace(PREFIXED_ID, " id ").replace(HEX_RUN, (run) => /\d/.test(run) ? " id " : run).replace(NUMBER_RUN, " # ").replace(/[^\p{L}\p{N}#]+/gu, " ").replace(/\s+/g, " ").trim();
@@ -16378,14 +17110,17 @@ function requestSignatures(text) {
16378
17110
  const statuses = [...text.matchAll(HTTP_STATUS)].map((m) => ({ status: m[1], at: m.index ?? 0, end: (m.index ?? 0) + m[0].length }));
16379
17111
  if (statuses.length === 0)
16380
17112
  return [];
16381
- const paths = [...text.matchAll(PATH_IN_TEXT)].map((m) => ({
16382
- method: m[1] ?? null,
16383
- path: normalizeBugPath((m[2] ?? m[3] ?? "").replace(/[.,;:]+$/, "")),
16384
- // Before normalisation, which turns a segment with a digit (favicon-32x32.png) into :id.
16385
- incidental: isIncidentalAssetPath((m[2] ?? m[3] ?? "").replace(/[.,;:]+$/, "").split(/[?#]/, 1)[0].toLowerCase()),
16386
- at: m.index ?? 0,
16387
- end: (m.index ?? 0) + m[0].length
16388
- })).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);
16389
17124
  const out = /* @__PURE__ */ new Map();
16390
17125
  for (const { status, at, end } of statuses) {
16391
17126
  const lead = STATUS_TO_PATH.exec(text.slice(end, end + 40));
@@ -16495,11 +17230,12 @@ function dedupeSiteWideSuspectedBugs(drafts) {
16495
17230
  delete plans.suspectedBugs;
16496
17231
  }
16497
17232
  }
16498
- 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;
16499
17234
  var init_survey_bug_signature = __esm({
16500
17235
  "../shared/dist/survey-bug-signature.js"() {
16501
17236
  "use strict";
16502
17237
  init_trailing_slashes();
17238
+ PATH_TRAILING_PUNCTUATION = /[.,;:]/;
16503
17239
  SIGNATURE_SOURCE_MAX_CHARS = 800;
16504
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;
16505
17241
  PREFIXED_ID = /\b[a-z]{1,8}[-_]\d+\b/g;
@@ -17830,7 +18566,7 @@ function pathIdsIn(text) {
17830
18566
  function findCounterIdPins(code) {
17831
18567
  if (code.length > CLOCK_MAX_CODE_CHARS)
17832
18568
  return [];
17833
- const clean2 = stripLineComments(blankBlockComments3(code));
18569
+ const clean3 = stripLineComments(blankBlockComments3(code));
17834
18570
  const fillValues = [];
17835
18571
  const candidates = [];
17836
18572
  let seenWrite = false;
@@ -17838,24 +18574,24 @@ function findCounterIdPins(code) {
17838
18574
  CALL_RE.lastIndex = 0;
17839
18575
  let m;
17840
18576
  let calls = 0;
17841
- while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean2)) !== null) {
18577
+ while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean3)) !== null) {
17842
18578
  const name = m[1];
17843
18579
  const open = m.index + m[0].length - 1;
17844
18580
  if (VALUE_WRITES.has(name)) {
17845
18581
  seenWrite = true;
17846
- const args2 = argsAt(clean2, open);
18582
+ const args2 = argsAt(clean3, open);
17847
18583
  if (args2 !== null)
17848
18584
  fillValues.push(...staticStrings(args2));
17849
18585
  continue;
17850
18586
  }
17851
18587
  if (createIndex === -1) {
17852
- if (name === "click" && seenWrite && isCreateClick(clean2, m.index))
18588
+ if (name === "click" && seenWrite && isCreateClick(clean3, m.index))
17853
18589
  createIndex = m.index;
17854
18590
  continue;
17855
18591
  }
17856
18592
  if (name === "click")
17857
18593
  continue;
17858
- const args = argsAt(clean2, open);
18594
+ const args = argsAt(clean3, open);
17859
18595
  if (args === null)
17860
18596
  continue;
17861
18597
  const isUrl = URL_SINKS.has(name);
@@ -17874,7 +18610,7 @@ function findCounterIdPins(code) {
17874
18610
  }
17875
18611
  if (createIndex === -1)
17876
18612
  return [];
17877
- const before = clean2.slice(0, createIndex);
18613
+ const before = clean3.slice(0, createIndex);
17878
18614
  let pathIdsBefore = null;
17879
18615
  const findings = [];
17880
18616
  const seen = /* @__PURE__ */ new Set();
@@ -17898,7 +18634,7 @@ function findCounterIdPins(code) {
17898
18634
  continue;
17899
18635
  }
17900
18636
  }
17901
- 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) });
17902
18638
  }
17903
18639
  return findings;
17904
18640
  }
@@ -18193,7 +18929,7 @@ var init_suspicious_expectation = __esm({
18193
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;
18194
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;
18195
18931
  STUCK_VETO_RE = new RegExp(`${NOT_STUCK_RE.source}|${LISTS_STUCK_RE.source}`, "i");
18196
- 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;
18197
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)";
18198
18934
  ELLIPSIS = "(?:\\.{3}|\u2026)";
18199
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;
@@ -18349,7 +19085,7 @@ function outcomeNamedByAssertion(outcome, code) {
18349
19085
  function namedByStatements(outcome, statements) {
18350
19086
  const text = outcomeText(outcome);
18351
19087
  if (outcome.type === "url") {
18352
- 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);
18353
19089
  return statements.some((stmt) => {
18354
19090
  if (!/\.(?:waitForURL|toHaveURL)\s{0,20}\(/.test(stmt))
18355
19091
  return false;
@@ -18521,7 +19257,7 @@ function evaluatePromotionGate(input, checks = PROMOTION_QUALITY_CHECKS) {
18521
19257
  ...suspiciousTagged ? { suspiciousPass: { mode: suspicious, covered: suspiciousCovered, wouldHold: suspiciousHold } } : {}
18522
19258
  };
18523
19259
  }
18524
- 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;
18525
19261
  var init_promotion_quality_gate = __esm({
18526
19262
  "../shared/dist/promotion-quality-gate.js"() {
18527
19263
  "use strict";
@@ -18536,11 +19272,12 @@ var init_promotion_quality_gate = __esm({
18536
19272
  init_run_classification();
18537
19273
  init_expected_outcome_coverage();
18538
19274
  init_expect_call();
19275
+ init_trailing_slashes();
18539
19276
  SOURCE_RANK = { generator: 0, classifier: 0, healer: 1, human: 2 };
18540
19277
  strengthCheck = {
18541
19278
  id: "assertion-strength",
18542
- run({ code, tags, strengthGate, flagContradictedAdditions }) {
18543
- const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true });
19279
+ run({ code, tags, strengthGate, flagContradictedAdditions, lastActionProof }) {
19280
+ const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true, lastActionProof });
18544
19281
  if (verdict.severity === "route-bug")
18545
19282
  return [{ check: "route-bug" }];
18546
19283
  const authDowngrade = verdict.severity === "downgrade" && tags.some((t) => t.toLowerCase() === "@auth");
@@ -18558,7 +19295,7 @@ var init_promotion_quality_gate = __esm({
18558
19295
  MAX_STATEMENTS = 500;
18559
19296
  MAX_STATEMENT_CHARS = 400;
18560
19297
  LITERAL_OR_IDENTIFIER = /'(?:[^'\\\n]|\\.){0,500}'|"(?:[^"\\\n]|\\.){0,500}"|`(?:[^`\\]|\\.){0,2000}`|(?<![\w$.])[A-Za-z_$][\w$]{0,60}/g;
18561
- 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}\(/;
18562
19299
  FEEDBACK_REGION = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/i;
18563
19300
  ONE_SHOT_MESSAGE_REASON = "one-shot success/error banner text";
18564
19301
  FEEDBACK_REGION_ALL = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/gi;
@@ -18617,6 +19354,7 @@ var init_promotion_quality_gate = __esm({
18617
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}\)/
18618
19355
  };
18619
19356
  ROUTE_TERM = /\/[A-Za-z0-9_./?&=#-]{1,200}/g;
19357
+ ROUTE_TRAILING_PUNCTUATION = /[.,;:!?)]/;
18620
19358
  QUOTED_LITERAL = /'([^'\\\n]{3,200})'|"([^"\\\n]{3,200})"|`([^`\\$\n]{3,200})`/g;
18621
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;
18622
19360
  MAX_OUTCOME_STATEMENTS = 300;
@@ -18736,8 +19474,8 @@ ${goalText}`);
18736
19474
  return count3 > 0 ? [{ check: "locator-fragment", detail: String(count3), soft: true }] : [];
18737
19475
  }
18738
19476
  };
18739
- 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;
18740
- 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*\(/;
18741
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;
18742
19480
  MAX_FILTER_LINES = 2e3;
18743
19481
  MAX_FILTER_LINE_CHARS = 600;
@@ -18852,7 +19590,7 @@ ${goalText}`);
18852
19590
  });
18853
19591
 
18854
19592
  // ../shared/dist/regex-alternation.js
18855
- function closingParens(source) {
19593
+ function closingParens2(source) {
18856
19594
  const close = new Int32Array(source.length).fill(-1);
18857
19595
  const open = [];
18858
19596
  let inClass = false;
@@ -18877,7 +19615,7 @@ function closingParens(source) {
18877
19615
  return close;
18878
19616
  }
18879
19617
  function alternatesAtTop(source) {
18880
- const close = closingParens(source);
19618
+ const close = closingParens2(source);
18881
19619
  let start = source.startsWith("^") ? 1 : 0;
18882
19620
  let end = source.endsWith("$") ? source.length - 1 : source.length;
18883
19621
  while (start < end && source[start] === "(" && close[start] === end - 1) {
@@ -18926,6 +19664,124 @@ var init_page_evidence = __esm({
18926
19664
  }
18927
19665
  });
18928
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
+
18929
19785
  // ../shared/dist/index.js
18930
19786
  var dist_exports = {};
18931
19787
  __export(dist_exports, {
@@ -18933,9 +19789,11 @@ __export(dist_exports, {
18933
19789
  ACTION_BATCH_MAX_SIZE: () => ACTION_BATCH_MAX_SIZE,
18934
19790
  ADDON_DEFINITIONS: () => ADDON_DEFINITIONS,
18935
19791
  ADMIN_EMAIL_LIMITS: () => ADMIN_EMAIL_LIMITS,
19792
+ ADMIN_FORCE_CANCELLED_RUN_ERROR: () => ADMIN_FORCE_CANCELLED_RUN_ERROR,
18936
19793
  ADMIN_VIEW_DEFAULT_RETURN_TO: () => ADMIN_VIEW_DEFAULT_RETURN_TO,
18937
19794
  AGENT_AI_PROVIDER_PREFIXES: () => AGENT_AI_PROVIDER_PREFIXES,
18938
19795
  AI_CREDIT_MARGIN: () => AI_CREDIT_MARGIN,
19796
+ ALLOWED_PROVIDER_CHAIN_HOPS: () => ALLOWED_PROVIDER_CHAIN_HOPS,
18939
19797
  API_AUTH_HEADERS_ENV_KEY: () => API_AUTH_HEADERS_ENV_KEY,
18940
19798
  API_MODEL_CATALOG: () => API_MODEL_CATALOG,
18941
19799
  API_MODEL_FIELDS: () => API_MODEL_FIELDS,
@@ -18975,12 +19833,14 @@ __export(dist_exports, {
18975
19833
  BUG_TEST_BROKEN_REASON: () => BUG_TEST_BROKEN_REASON,
18976
19834
  BUG_TEST_BROKEN_TAG: () => BUG_TEST_BROKEN_TAG,
18977
19835
  CACHE_PRICE_PER_M: () => CACHE_PRICE_PER_M,
19836
+ CANCELLED_REASON_PREFIXES: () => CANCELLED_REASON_PREFIXES,
18978
19837
  CANVAS_UNSUPPORTED_BODY: () => CANVAS_UNSUPPORTED_BODY,
18979
19838
  CANVAS_UNSUPPORTED_CODE: () => CANVAS_UNSUPPORTED_CODE,
18980
19839
  CANVAS_UNSUPPORTED_NOT_CHARGED: () => CANVAS_UNSUPPORTED_NOT_CHARGED,
18981
19840
  CANVAS_UNSUPPORTED_OVERRIDE_HINT: () => CANVAS_UNSUPPORTED_OVERRIDE_HINT,
18982
19841
  CANVAS_UNSUPPORTED_TITLE: () => CANVAS_UNSUPPORTED_TITLE,
18983
19842
  CAPTURE_JSON_STRUCTURE_LIMITS: () => CAPTURE_JSON_STRUCTURE_LIMITS,
19843
+ CHAIN_CANCELLED_RUN_ERROR: () => CHAIN_CANCELLED_RUN_ERROR,
18984
19844
  CHAT_FAILOVER_KEY: () => CHAT_FAILOVER_KEY,
18985
19845
  CLAUDE_CLI_DEFAULT_EFFORT: () => CLAUDE_CLI_DEFAULT_EFFORT,
18986
19846
  CLAUDE_CLI_DEFAULT_MODEL: () => CLAUDE_CLI_DEFAULT_MODEL,
@@ -19017,6 +19877,7 @@ __export(dist_exports, {
19017
19877
  DEFAULT_GEMINI_SCREEN_MODEL: () => DEFAULT_GEMINI_SCREEN_MODEL,
19018
19878
  DEFAULT_JSON_STRUCTURE_LIMITS: () => DEFAULT_JSON_STRUCTURE_LIMITS,
19019
19879
  DEFAULT_PLATFORM_DB_POOL_SIZE: () => DEFAULT_PLATFORM_DB_POOL_SIZE,
19880
+ DEFAULT_PROVIDER_ACCOUNT_ID: () => DEFAULT_PROVIDER_ACCOUNT_ID,
19020
19881
  DEFAULT_SHARED_DB_POOL_SIZE: () => DEFAULT_SHARED_DB_POOL_SIZE,
19021
19882
  DEFAULT_STT_MODEL: () => DEFAULT_STT_MODEL,
19022
19883
  DEFAULT_STT_MODEL_BY_PROVIDER: () => DEFAULT_STT_MODEL_BY_PROVIDER,
@@ -19033,6 +19894,7 @@ __export(dist_exports, {
19033
19894
  ENVIRONMENTAL_ERROR_STORED_PREFIXES: () => ENVIRONMENTAL_ERROR_STORED_PREFIXES,
19034
19895
  EVIDENCE_NOTE_PREFIXES: () => EVIDENCE_NOTE_PREFIXES,
19035
19896
  EXPECT_CALL_RE: () => EXPECT_CALL_RE,
19897
+ FEATURE_ROW_RUN_WINDOW: () => FEATURE_ROW_RUN_WINDOW,
19036
19898
  FLOW_PHASE_LABELS: () => FLOW_PHASE_LABELS,
19037
19899
  FLOW_PHASE_TYPES: () => FLOW_PHASE_TYPES,
19038
19900
  FLOW_SERVER_TYPES: () => FLOW_SERVER_TYPES,
@@ -19100,6 +19962,8 @@ __export(dist_exports, {
19100
19962
  MAX_ORG_NAME_LENGTH: () => MAX_ORG_NAME_LENGTH,
19101
19963
  MAX_PASSWORD_BYTES: () => MAX_PASSWORD_BYTES,
19102
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,
19103
19967
  MAX_SURVEY_ENTRY_URLS: () => MAX_SURVEY_ENTRY_URLS,
19104
19968
  MAX_SURVEY_ENTRY_URL_LENGTH: () => MAX_SURVEY_ENTRY_URL_LENGTH,
19105
19969
  MCP_ARMY_LIMITS: () => MCP_ARMY_LIMITS,
@@ -19135,7 +19999,7 @@ __export(dist_exports, {
19135
19999
  MUSE_ENGINE_DEFAULT_MODEL: () => MUSE_ENGINE_DEFAULT_MODEL,
19136
20000
  MUSE_ENGINE_MODELS: () => MUSE_ENGINE_MODELS,
19137
20001
  NOREPLY_EMAIL: () => NOREPLY_EMAIL,
19138
- OPENCODE_GO_2_PREFIX: () => OPENCODE_GO_2_PREFIX,
20002
+ OPENCODE_GO_DEEPSEEK_GLM_MODELS: () => OPENCODE_GO_DEEPSEEK_GLM_MODELS,
19139
20003
  OPENCODE_GO_MODEL_PATTERN: () => OPENCODE_GO_MODEL_PATTERN,
19140
20004
  OPENCODE_GO_PREFIX: () => OPENCODE_GO_PREFIX,
19141
20005
  OPENROUTER_MAX_CALL_COST_USD: () => OPENROUTER_MAX_CALL_COST_USD,
@@ -19155,10 +20019,12 @@ __export(dist_exports, {
19155
20019
  PLAN_FEATURES: () => PLAN_FEATURES,
19156
20020
  PLAN_FEATURE_LOCKED_CODE: () => PLAN_FEATURE_LOCKED_CODE,
19157
20021
  PLAN_ORDER: () => PLAN_ORDER,
20022
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR: () => PLATFORM_ADMIN_CANCELLED_RUN_ERROR,
19158
20023
  PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
19159
20024
  PLATFORM_DEFAULT_PROVIDER: () => PLATFORM_DEFAULT_PROVIDER,
19160
20025
  PLATFORM_PLAN_BILLING: () => PLATFORM_PLAN_BILLING,
19161
20026
  PLATFORM_SERVER_APPLICATION_NAME: () => PLATFORM_SERVER_APPLICATION_NAME,
20027
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR: () => PLATFORM_SUPPORT_CANCELLED_RUN_ERROR,
19162
20028
  POST_SPECIAL_TRIAL_DAYS: () => POST_SPECIAL_TRIAL_DAYS,
19163
20029
  PRESET_DIRECT_FALLBACK: () => PRESET_DIRECT_FALLBACK,
19164
20030
  PRODUCTION_READ_ONLY_GUARD_TEMPLATE: () => PRODUCTION_READ_ONLY_GUARD_TEMPLATE,
@@ -19170,8 +20036,12 @@ __export(dist_exports, {
19170
20036
  PROJECT_OVERRIDE_FIELDS: () => PROJECT_OVERRIDE_FIELDS,
19171
20037
  PROMOTION_GATE_MAX_CODE_CHARS: () => PROMOTION_GATE_MAX_CODE_CHARS,
19172
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,
19173
20041
  PROVIDER_CHAINS: () => PROVIDER_CHAINS,
20042
+ PROVIDER_CHAINS_SETTING_MAX_CHARS: () => PROVIDER_CHAINS_SETTING_MAX_CHARS,
19174
20043
  PROVIDER_CHAIN_LEADS: () => PROVIDER_CHAIN_LEADS,
20044
+ PROVIDER_CHAIN_MAX_ENTRIES: () => PROVIDER_CHAIN_MAX_ENTRIES,
19175
20045
  PROVIDER_HOST_NOT_OFFERED: () => PROVIDER_HOST_NOT_OFFERED,
19176
20046
  PUBLIC_PLANS: () => PUBLIC_PLANS,
19177
20047
  QUARANTINE_CLOSE_TAGS: () => QUARANTINE_CLOSE_TAGS,
@@ -19179,10 +20049,12 @@ __export(dist_exports, {
19179
20049
  READ_ONLY_TAGS: () => READ_ONLY_TAGS,
19180
20050
  RECHECKED_NOTES: () => RECHECKED_NOTES,
19181
20051
  RECORDING_VALUE_PLACEHOLDERS: () => RECORDING_VALUE_PLACEHOLDERS,
20052
+ RENDER_IDENTITY_MAX_LENGTH: () => RENDER_IDENTITY_MAX_LENGTH,
19182
20053
  REPOSITORY_CLIS: () => REPOSITORY_CLIS,
19183
20054
  RESERVED_PROJECT_KEYS: () => RESERVED_PROJECT_KEYS,
19184
20055
  RESULT_STRUCTURE_TOO_COSTLY_CODE: () => RESULT_STRUCTURE_TOO_COSTLY_CODE,
19185
20056
  RETRIED_ATTEMPTS_KEY: () => RETRIED_ATTEMPTS_KEY,
20057
+ RETRY_HELD_RUN_ERROR: () => RETRY_HELD_RUN_ERROR,
19186
20058
  RUNNER_INTERRUPT_ERROR_PREFIX: () => RUNNER_INTERRUPT_ERROR_PREFIX,
19187
20059
  RUN_INTERRUPTED_ERROR_PREFIXES: () => RUN_INTERRUPTED_ERROR_PREFIXES,
19188
20060
  RUN_INTERRUPTION_NOT_RETRIED_MARKERS: () => RUN_INTERRUPTION_NOT_RETRIED_MARKERS,
@@ -19195,6 +20067,8 @@ __export(dist_exports, {
19195
20067
  SHARED_SERVER_APPLICATION_NAME: () => SHARED_SERVER_APPLICATION_NAME,
19196
20068
  SPEND_SOURCE_KEY: () => SPEND_SOURCE_KEY,
19197
20069
  SPEND_SOURCE_SERVER: () => SPEND_SOURCE_SERVER,
20070
+ STATED_LIMITS_VERSION: () => STATED_LIMITS_VERSION,
20071
+ STATED_PATTERN_MAX: () => STATED_PATTERN_MAX,
19198
20072
  STATIC_STANDARD_MIN_INTERACTIVE: () => STATIC_STANDARD_MIN_INTERACTIVE,
19199
20073
  STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
19200
20074
  SUCCESS_NOTICE_TEXT: () => SUCCESS_NOTICE_TEXT,
@@ -19224,14 +20098,20 @@ __export(dist_exports, {
19224
20098
  UI_VARIANTS: () => UI_VARIANTS,
19225
20099
  UNEXPECTED_PASS_ERROR_PREFIX: () => UNEXPECTED_PASS_ERROR_PREFIX,
19226
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,
19227
20105
  VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
19228
20106
  WEB_PROXY_CHAIN_HEADER: () => WEB_PROXY_CHAIN_HEADER,
19229
20107
  WITHHELD_CREDENTIAL_TEXT: () => WITHHELD_CREDENTIAL_TEXT,
19230
20108
  ZAI_MODEL_PATTERN: () => ZAI_MODEL_PATTERN,
19231
20109
  ZAI_PREFIX: () => ZAI_PREFIX,
20110
+ accountServesModel: () => accountServesModel,
19232
20111
  accumulateStatements: () => accumulateStatements,
19233
20112
  adoptBrokerFailover: () => adoptBrokerFailover,
19234
20113
  aiCostToCreditCents: () => aiCostToCreditCents,
20114
+ allowedProviderChainHosts: () => allowedProviderChainHosts,
19235
20115
  alternatesAtTop: () => alternatesAtTop,
19236
20116
  analyzeDerivedValues: () => analyzeDerivedValues,
19237
20117
  analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
@@ -19244,6 +20124,7 @@ __export(dist_exports, {
19244
20124
  analyzeUnreachedUrlWaits: () => analyzeUnreachedUrlWaits,
19245
20125
  analyzeUrlWaits: () => analyzeUrlWaits,
19246
20126
  analyzeWriteResponse: () => analyzeWriteResponse,
20127
+ androidResourceOwners: () => androidResourceOwners,
19247
20128
  apiModelCardsFromConfig: () => apiModelCardsFromConfig,
19248
20129
  apiModelsForProvider: () => apiModelsForProvider,
19249
20130
  apiProviderModelLabel: () => apiProviderModelLabel,
@@ -19254,6 +20135,7 @@ __export(dist_exports, {
19254
20135
  applyLaunchSpecialPlan: () => applyLaunchSpecialPlan,
19255
20136
  asPlainExpect: () => asPlainExpect,
19256
20137
  assertionOnlyCode: () => assertionOnlyCode,
20138
+ assertionStrengthHoistedReadsOn: () => assertionStrengthHoistedReadsOn,
19257
20139
  assertionsReadVisibleContent: () => assertionsReadVisibleContent,
19258
20140
  assessCodeOutcomeCoverage: () => assessCodeOutcomeCoverage,
19259
20141
  assessTitleCoverage: () => assessTitleCoverage,
@@ -19337,6 +20219,7 @@ __export(dist_exports, {
19337
20219
  deepgramSpeak: () => deepgramSpeak,
19338
20220
  deepgramTranscribe: () => deepgramTranscribe,
19339
20221
  defaultConfirmModel: () => defaultConfirmModel,
20222
+ defaultProviderChainEntries: () => defaultProviderChainEntries,
19340
20223
  defaultScreenModel: () => defaultScreenModel,
19341
20224
  defaultVoiceForTtsModel: () => defaultVoiceForTtsModel,
19342
20225
  deriveAuthPlan: () => deriveAuthPlan,
@@ -19379,6 +20262,7 @@ __export(dist_exports, {
19379
20262
  extractOutcomeSearchTerms: () => extractOutcomeSearchTerms,
19380
20263
  featureForAiCallType: () => featureForAiCallType,
19381
20264
  featureForMcpTool: () => featureForMcpTool,
20265
+ featureRowRunStatus: () => featureRowRunStatus,
19382
20266
  findBracedClause: () => findBracedClause,
19383
20267
  findCounterIdPins: () => findCounterIdPins,
19384
20268
  findCredentialTextLeftover: () => findCredentialTextLeftover,
@@ -19427,6 +20311,7 @@ __export(dist_exports, {
19427
20311
  getPlanChangeType: () => getPlanChangeType,
19428
20312
  getPlanDefinition: () => getPlanDefinition,
19429
20313
  getPlanLabel: () => getPlanLabel,
20314
+ getProviderChainOverrides: () => getProviderChainOverrides,
19430
20315
  getTrialDaysRemaining: () => getTrialDaysRemaining,
19431
20316
  googleBrokerNavigationUrl: () => googleBrokerNavigationUrl,
19432
20317
  googleBrokerUnavailableLine: () => googleBrokerUnavailableLine,
@@ -19440,10 +20325,10 @@ __export(dist_exports, {
19440
20325
  hasEncodedRequestBody: () => hasEncodedRequestBody,
19441
20326
  hasEngineSpecParts: () => hasEngineSpecParts,
19442
20327
  hasOnlyConstantAssertions: () => hasOnlyConstantAssertions,
19443
- hasOpenCodeGo2Prefix: () => hasOpenCodeGo2Prefix,
19444
20328
  hasOpenCodeGoPrefix: () => hasOpenCodeGoPrefix,
19445
20329
  hasOpenRouterPrefix: () => hasOpenRouterPrefix,
19446
20330
  hasPlatformLocator: () => hasPlatformLocator,
20331
+ hasProviderChainLeadsByDefault: () => hasProviderChainLeadsByDefault,
19447
20332
  hasReadOnlyMarker: () => hasReadOnlyMarker,
19448
20333
  hasSurveyNotes: () => hasSurveyNotes,
19449
20334
  hasTestFailContract: () => hasTestFailContract,
@@ -19469,8 +20354,11 @@ __export(dist_exports, {
19469
20354
  isAuthTokenName: () => isAuthTokenName,
19470
20355
  isBlockedEnvVar: () => isBlockedEnvVar,
19471
20356
  isBrowserCliEngine: () => isBrowserCliEngine,
20357
+ isBrowserVersion: () => isBrowserVersion,
19472
20358
  isBugTestBrokenError: () => isBugTestBrokenError,
20359
+ isCancelledReason: () => isCancelledReason,
19473
20360
  isCanvasUnsupportedMessage: () => isCanvasUnsupportedMessage,
20361
+ isChainablePrimary: () => isChainablePrimary,
19474
20362
  isCliEngineModelId: () => isCliEngineModelId,
19475
20363
  isCliEngineSpec: () => isCliEngineSpec,
19476
20364
  isCodeFactNote: () => isCodeFactNote,
@@ -19494,6 +20382,7 @@ __export(dist_exports, {
19494
20382
  isIdentityMobileXpath: () => isIdentityMobileXpath,
19495
20383
  isIdentityProviderSurface: () => isIdentityProviderSurface,
19496
20384
  isIncidentalAssetPath: () => isIncidentalAssetPath,
20385
+ isIntentionalNegativeAuthTest: () => isIntentionalNegativeAuthTest,
19497
20386
  isIpOrLocalhost: () => isIpOrLocalhost,
19498
20387
  isKnownPageType: () => isKnownPageType,
19499
20388
  isLaunchSpecialActive: () => isLaunchSpecialActive,
@@ -19521,6 +20410,7 @@ __export(dist_exports, {
19521
20410
  isRunInterruptedError: () => isRunInterruptedError,
19522
20411
  isRunnableStep: () => isRunnableStep,
19523
20412
  isSafeDeepLink: () => isSafeDeepLink,
20413
+ isSafeMaskSelector: () => isSafeMaskSelector,
19524
20414
  isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
19525
20415
  isSelectableApiModel: () => isSelectableApiModel,
19526
20416
  isSelfServePlan: () => isSelfServePlan,
@@ -19584,6 +20474,7 @@ __export(dist_exports, {
19584
20474
  normalizeFinishReason: () => normalizeFinishReason,
19585
20475
  normalizeNoteText: () => normalizeNoteText,
19586
20476
  normalizePageType: () => normalizePageType,
20477
+ normalizeProviderAccountModels: () => normalizeProviderAccountModels,
19587
20478
  normalizeTtsModel: () => normalizeTtsModel,
19588
20479
  normalizeTtsVoiceId: () => normalizeTtsVoiceId,
19589
20480
  noteChatFailover: () => noteChatFailover,
@@ -19608,13 +20499,18 @@ __export(dist_exports, {
19608
20499
  parseGeminiModelsPayload: () => parseGeminiModelsPayload,
19609
20500
  parseGoogleAuthorizeUrl: () => parseGoogleAuthorizeUrl,
19610
20501
  parseGoogleGisRequestUrl: () => parseGoogleGisRequestUrl,
20502
+ parseLastActionProofMode: () => parseLastActionProofMode,
19611
20503
  parseLimitParam: () => parseLimitParam,
19612
20504
  parseOffsetParam: () => parseOffsetParam,
19613
20505
  parsePageParam: () => parsePageParam,
19614
20506
  parsePgIntSetting: () => parsePgIntSetting,
19615
20507
  parseProjectEngineOverrides: () => parseProjectEngineOverrides,
19616
20508
  parsePromotionGateMode: () => parsePromotionGateMode,
20509
+ parseProviderAccountLists: () => parseProviderAccountLists,
20510
+ parseProviderChainOverrides: () => parseProviderChainOverrides,
19617
20511
  parseRecheckClearMode: () => parseRecheckClearMode,
20512
+ parseRenderIdentity: () => parseRenderIdentity,
20513
+ parseStoredProviderChainOverrides: () => parseStoredProviderChainOverrides,
19618
20514
  parseSurveyEntryUrls: () => parseSurveyEntryUrls,
19619
20515
  parseSuspiciousPassMode: () => parseSuspiciousPassMode,
19620
20516
  parseTaskModelUpdate: () => parseTaskModelUpdate,
@@ -19645,7 +20541,9 @@ __export(dist_exports, {
19645
20541
  projectAppHosts: () => projectAppHosts,
19646
20542
  providerChainFor: () => providerChainFor,
19647
20543
  providerChainHostOf: () => providerChainHostOf,
20544
+ providerChainHostProvider: () => providerChainHostProvider,
19648
20545
  providerChainLeadsFor: () => providerChainLeadsFor,
20546
+ providerChainModelKey: () => providerChainModelKey,
19649
20547
  providerChainPrimariesForHop: () => providerChainPrimariesForHop,
19650
20548
  providerHostPlan: () => providerHostPlan,
19651
20549
  readBalanced: () => readBalanced,
@@ -19655,6 +20553,7 @@ __export(dist_exports, {
19655
20553
  readEnabledEngines: () => readEnabledEngines,
19656
20554
  readProjectEngineOverrides: () => readProjectEngineOverrides,
19657
20555
  readRetriedAttempts: () => readRetriedAttempts,
20556
+ readStatedLimits: () => readStatedLimits,
19658
20557
  readStaticPageCanvasSignals: () => readStaticPageCanvasSignals,
19659
20558
  readSurveyEntryUrls: () => readSurveyEntryUrls,
19660
20559
  readSurveyNotes: () => readSurveyNotes,
@@ -19667,6 +20566,7 @@ __export(dist_exports, {
19667
20566
  regexLiteralEnd: () => regexLiteralEnd2,
19668
20567
  regexOpensAfter: () => regexOpensAfter,
19669
20568
  registrableDomain: () => registrableDomain,
20569
+ renderIdentity: () => renderIdentity,
19670
20570
  renderSurveyNotesForPrompt: () => renderSurveyNotesForPrompt,
19671
20571
  repairPageType: () => repairPageType,
19672
20572
  replaceBlockComments: () => replaceBlockComments,
@@ -19714,11 +20614,14 @@ __export(dist_exports, {
19714
20614
  selectorCandidatesForStep: () => selectorCandidatesForStep,
19715
20615
  selectorForValue: () => selectorForValue,
19716
20616
  setAgenticCliRuntime: () => setAgenticCliRuntime,
20617
+ setProviderChainOverrides: () => setProviderChainOverrides,
19717
20618
  signupPlanFor: () => signupPlanFor,
19718
20619
  simplifyMobileWanderingInPlace: () => simplifyMobileWanderingInPlace,
19719
20620
  siteOf: () => siteOf,
19720
20621
  specItemsFromTestPlans: () => specItemsFromTestPlans,
19721
20622
  splitAnswerLines: () => splitAnswerLines,
20623
+ statedLimitsFromAttrs: () => statedLimitsFromAttrs,
20624
+ statedLimitsRecord: () => statedLimitsRecord,
19722
20625
  stepAppliesToPlatform: () => stepAppliesToPlatform,
19723
20626
  stepConsoleLines: () => stepConsoleLines,
19724
20627
  stepIsDivergentForPlatform: () => stepIsDivergentForPlatform,
@@ -19730,7 +20633,10 @@ __export(dist_exports, {
19730
20633
  stripCodeFactPrefix: () => stripCodeFactPrefix,
19731
20634
  stripCoverageComments: () => stripCoverageComments,
19732
20635
  stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
20636
+ stripFractionZeros: () => stripFractionZeros,
20637
+ stripLineComment: () => stripLineComment,
19733
20638
  stripLoginScaffolding: () => stripLoginScaffolding,
20639
+ stripTrailingChars: () => stripTrailingChars,
19734
20640
  stripTrailingSlashes: () => stripTrailingSlashes,
19735
20641
  strongerNeedsReviewSource: () => strongerNeedsReviewSource,
19736
20642
  sttModelsForProvider: () => sttModelsForProvider,
@@ -19748,10 +20654,14 @@ __export(dist_exports, {
19748
20654
  ttsFallbackModel: () => ttsFallbackModel,
19749
20655
  ttsProviderForModel: () => ttsProviderForModel,
19750
20656
  unexpectedPassError: () => unexpectedPassError,
20657
+ v1RenderIdentity: () => v1RenderIdentity,
19751
20658
  validateAppMapDocument: () => validateAppMapDocument,
19752
20659
  validateAssertionStrength: () => validateAssertionStrength,
19753
20660
  validateEngineSpec: () => validateEngineSpec,
20661
+ validateProviderChainEntries: () => validateProviderChainEntries,
20662
+ validateProviderChainsSetting: () => validateProviderChainsSetting,
19754
20663
  validateTaskModelId: () => validateTaskModelId,
20664
+ visualBaselinesEnabled: () => visualBaselinesEnabled,
19755
20665
  visualProDailyBudget: () => visualProDailyBudget,
19756
20666
  voiceMatchesTtsModel: () => voiceMatchesTtsModel,
19757
20667
  voicesForTtsModel: () => voicesForTtsModel,
@@ -19814,11 +20724,14 @@ var init_dist = __esm({
19814
20724
  init_deepgram_voices();
19815
20725
  init_feature_flags();
19816
20726
  init_env_flags();
20727
+ init_visual_compare_limits();
20728
+ init_visual_baseline_identity();
19817
20729
  init_analytics_url();
19818
20730
  init_agentic_cli_runtime();
19819
20731
  init_api_model_catalog();
19820
20732
  init_engine_spec();
19821
20733
  init_provider_chains();
20734
+ init_provider_accounts();
19822
20735
  init_project_engine_overrides();
19823
20736
  init_ai_profiles();
19824
20737
  init_openrouter_usage();
@@ -19860,6 +20773,7 @@ var init_dist = __esm({
19860
20773
  init_regex_alternation();
19861
20774
  init_page_evidence();
19862
20775
  init_trailing_slashes();
20776
+ init_stated_limits();
19863
20777
  }
19864
20778
  });
19865
20779
 
@@ -19888,11 +20802,11 @@ export {
19888
20802
  stepsNeedingReviewForPlatform,
19889
20803
  parseBoolFlag,
19890
20804
  decideHarPosted,
20805
+ deriveAuthPlan,
19891
20806
  isConfigLikeReason,
19892
20807
  classifyErrorMessage,
19893
20808
  isAppOriginResolutionEnabled,
19894
20809
  resolveCrossOriginConfigOverride,
19895
- deriveAuthPlan,
19896
20810
  isSharedAuthInjectEnabled,
19897
20811
  isRepositoryCli,
19898
20812
  repositoryCliLabel,