@validate.qa/runner 1.0.103 → 1.0.104

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 (32) hide show
  1. package/README.md +7 -2
  2. package/dist/{chunk-YI3ZC2XI.mjs → chunk-6N4QCCEH.mjs} +2 -2
  3. package/dist/{chunk-CMM4DOXC.mjs → chunk-EBWZ7NGU.mjs} +11 -8
  4. package/dist/{chunk-OTKJZIOD.mjs → chunk-EKY6CN4Y.mjs} +659 -122
  5. package/dist/{chunk-CG75I3ZZ.mjs → chunk-MEE4DXRX.mjs} +13437 -1386
  6. package/dist/{chunk-WVLBQDFP.mjs → chunk-OXDU6ARE.mjs} +8 -2
  7. package/dist/{chunk-AAUHXGDS.mjs → chunk-PUMRAFJS.mjs} +37 -13
  8. package/dist/{chunk-BECCGMU4.mjs → chunk-TGVKABFN.mjs} +45 -3
  9. package/dist/{chunk-YIM746WO.mjs → chunk-TS4OUJVT.mjs} +1 -1
  10. package/dist/{chunk-NNJYKR3O.mjs → chunk-UR7KDOTP.mjs} +29 -6
  11. package/dist/{chunk-36AWDKPL.mjs → chunk-UZ67UBXH.mjs} +14 -3
  12. package/dist/{chunk-7SH3COMQ.mjs → chunk-VWE3SQ2Z.mjs} +1 -1
  13. package/dist/{chunk-TK5KEGNW.mjs → chunk-XXZZMG6Q.mjs} +15 -3
  14. package/dist/cli.js +20962 -7343
  15. package/dist/cli.mjs +1159 -245
  16. package/dist/credential-mcp-server.js +671 -127
  17. package/dist/credential-mcp-server.mjs +3 -3
  18. package/dist/discovery-mcp-server.js +710 -129
  19. package/dist/discovery-mcp-server.mjs +4 -4
  20. package/dist/dist-BXHLIG5H.mjs +17 -0
  21. package/dist/inbox-mcp-server.js +664 -123
  22. package/dist/inbox-mcp-server.mjs +3 -3
  23. package/dist/route-seed-worker.js +671 -124
  24. package/dist/route-seed-worker.mjs +3 -3
  25. package/dist/run-write-guard-preload.js +713 -125
  26. package/dist/run-write-guard-preload.mjs +4 -4
  27. package/dist/run-write-guard.js +713 -125
  28. package/dist/run-write-guard.mjs +3 -3
  29. package/dist/tar-extract-child.js +38 -14
  30. package/dist/tar-extract-child.mjs +2 -2
  31. package/package.json +3 -2
  32. package/dist/dist-2G7YTNH5.mjs +0 -17
@@ -114,7 +114,13 @@ function buildSandboxedEnv(processEnv, testVariables) {
114
114
  return env;
115
115
  }
116
116
  function escapeEnvValue(value) {
117
- const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
117
+ if (!value.includes("\r")) {
118
+ for (const quote of ["'", "`"]) {
119
+ if (!value.includes(quote))
120
+ return `${quote}${value}${quote}`;
121
+ }
122
+ }
123
+ const escaped = value.replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
118
124
  return `"${escaped}"`;
119
125
  }
120
126
  var API_PREFIX, SUPPORT_EMAIL, PLATFORM_ADMIN_EMAIL, NOREPLY_EMAIL, EMAIL_FROM_IDENTITIES, ADMIN_EMAIL_LIMITS, AUDIO_CHUNK_DURATION_MS, ACTION_BATCH_INTERVAL_MS, ACTION_BATCH_MAX_SIZE, MOBILE_SCREENSHOT_INTERVAL_MS, MOBILE_ACTION_BATCH_MAX_SIZE, MOBILE_BRIDGE_PORT, MOBILE_SNAPSHOT_MAX_SIZE_BYTES, MOBILE_APP_ID_MAX_LENGTH, MOBILE_APP_ID_PATTERN, MOBILE_FOCUS_SETTLE_MS, MOBILE_POST_ACTION_SETTLE_MS, MOBILE_COLD_BOOT_SETTLE_MS, MOBILE_DEFAULT_SWIPE_DISTANCE, MOBILE_MAX_SWIPE_DISTANCE, SELECTOR_PRIORITIES, MOBILE_STEP_TYPES, BLOCKED_ENV_VARS, SPAWNED_ENV_ALLOWLIST, MAX_PLAYWRIGHT_CODE_LENGTH;
@@ -3036,6 +3042,65 @@ var init_discovery_har_decision = __esm({
3036
3042
  }
3037
3043
  });
3038
3044
 
3045
+ // ../shared/dist/regex-literal.js
3046
+ function opensRegexLiteral(code, i) {
3047
+ let j = i - 1;
3048
+ while (j >= 0 && (code[j] === " " || code[j] === " "))
3049
+ j--;
3050
+ if (j < 0 || code[j] === "\n" || code[j] === "\r")
3051
+ return true;
3052
+ return regexOpensAfter(code, j);
3053
+ }
3054
+ function regexOpensAfter(code, j) {
3055
+ const prev = code[j];
3056
+ if (REGEX_OPENERS.includes(prev))
3057
+ return !((prev === "+" || prev === "-") && code[j - 1] === prev);
3058
+ if (!/[a-z]/.test(prev))
3059
+ return false;
3060
+ let start = j;
3061
+ while (start > 0 && /[A-Za-z0-9_$]/.test(code[start - 1]))
3062
+ start--;
3063
+ if (start > 0 && code[start - 1] === ".")
3064
+ return false;
3065
+ return REGEX_KEYWORDS.has(code.slice(start, j + 1));
3066
+ }
3067
+ function regexLiteralEnd(code, i) {
3068
+ let k = i + 1;
3069
+ let inClass = false;
3070
+ while (k < code.length) {
3071
+ const ch = code[k];
3072
+ if (ch === "\n" || ch === "\r")
3073
+ return k;
3074
+ if (ch === "\\") {
3075
+ if (code[k + 1] === "\n" || code[k + 1] === "\r")
3076
+ return k + 1;
3077
+ k += 2;
3078
+ continue;
3079
+ }
3080
+ if (inClass) {
3081
+ if (ch === "]")
3082
+ inClass = false;
3083
+ } else if (ch === "[") {
3084
+ inClass = true;
3085
+ } else if (ch === "/") {
3086
+ k++;
3087
+ while (k < code.length && /[a-z]/i.test(code[k]))
3088
+ k++;
3089
+ return k;
3090
+ }
3091
+ k++;
3092
+ }
3093
+ return code.length;
3094
+ }
3095
+ var REGEX_OPENERS, REGEX_KEYWORDS;
3096
+ var init_regex_literal = __esm({
3097
+ "../shared/dist/regex-literal.js"() {
3098
+ "use strict";
3099
+ REGEX_OPENERS = "(,=:[!&|?{};+-*%~^<>";
3100
+ REGEX_KEYWORDS = /* @__PURE__ */ new Set(["return", "typeof", "case", "do", "else", "in", "of", "new", "delete", "void", "throw", "await", "yield", "instanceof"]);
3101
+ }
3102
+ });
3103
+
3039
3104
  // ../shared/dist/assertion-classifier.js
3040
3105
  function isButtonOrLinkLocator(line) {
3041
3106
  return /getByRole\s*\(\s*['"`](?:button|link)['"`]/i.test(line);
@@ -3344,7 +3409,7 @@ function classifyMobileAssertValue(value, context = {}) {
3344
3409
  return { tier: 2, reason: t2[1] };
3345
3410
  return null;
3346
3411
  }
3347
- function regexLiteralEnd(text, start) {
3412
+ function regexLiteralEnd2(text, start) {
3348
3413
  let inClass = false;
3349
3414
  for (let i = start + 1; i < text.length; i++) {
3350
3415
  const ch = text[i];
@@ -3370,7 +3435,7 @@ function parenDepthDelta(text) {
3370
3435
  let depth = 0;
3371
3436
  let quote = null;
3372
3437
  let escaped = false;
3373
- let prev = "";
3438
+ let prevAt = -1;
3374
3439
  let noRegexUntil = -1;
3375
3440
  for (let i = 0; i < text.length; i++) {
3376
3441
  const ch = text[i];
@@ -3385,7 +3450,7 @@ function parenDepthDelta(text) {
3385
3450
  if (quote) {
3386
3451
  if (ch === quote) {
3387
3452
  quote = null;
3388
- prev = ch;
3453
+ prevAt = i;
3389
3454
  }
3390
3455
  continue;
3391
3456
  }
@@ -3404,11 +3469,11 @@ function parenDepthDelta(text) {
3404
3469
  i = close + 1;
3405
3470
  continue;
3406
3471
  }
3407
- if (i > noRegexUntil && (prev === "" || REGEX_PRECEDER.has(prev))) {
3408
- const end = regexLiteralEnd(text, i);
3472
+ if (i > noRegexUntil && (prevAt < 0 || regexOpensAfter(text, prevAt))) {
3473
+ const end = regexLiteralEnd2(text, i);
3409
3474
  if (end > 0) {
3410
3475
  i = end - 1;
3411
- prev = "a";
3476
+ prevAt = end - 1;
3412
3477
  continue;
3413
3478
  }
3414
3479
  const nl = text.indexOf("\n", i);
@@ -3420,14 +3485,15 @@ function parenDepthDelta(text) {
3420
3485
  else if (ch === ")")
3421
3486
  depth--;
3422
3487
  if (ch !== " " && ch !== " " && ch !== "\n")
3423
- prev = ch;
3488
+ prevAt = i;
3424
3489
  }
3425
3490
  return depth;
3426
3491
  }
3427
- var PENDING_STATE_TEXT_REASON, PENDING_STATE_TEXT, TIER1_PATTERNS, TIER2_PATTERNS, TIER4_PATTERNS, SINGLE_QUOTED, DOUBLE_QUOTED, BACKTICK_QUOTED, QUOTED_BODY, LOCATOR_ARG_VALUE, LOCATOR_TEXT_ARG_PATTERNS, EXPECT_TOKEN, REGEX_PRECEDER;
3492
+ var PENDING_STATE_TEXT_REASON, PENDING_STATE_TEXT, TIER1_PATTERNS, TIER2_PATTERNS, TIER4_PATTERNS, SINGLE_QUOTED, DOUBLE_QUOTED, BACKTICK_QUOTED, QUOTED_BODY, LOCATOR_ARG_VALUE, LOCATOR_TEXT_ARG_PATTERNS, EXPECT_TOKEN;
3428
3493
  var init_assertion_classifier = __esm({
3429
3494
  "../shared/dist/assertion-classifier.js"() {
3430
3495
  "use strict";
3496
+ init_regex_literal();
3431
3497
  PENDING_STATE_TEXT_REASON = 'pending-state text ("Sending...") is transient';
3432
3498
  PENDING_STATE_TEXT = /\b(?!(?:Billing|Shipping|Pricing|Listing|Meeting|Morning|Evening|Warning|Building|Training|Marketing|Parking|Housing|Reading|Rating|Heading|Ceiling|Wedding|Clothing|Painting|Setting)\b)[A-Z][a-z]{2,}ing(?:\.{3}|…)/;
3433
3499
  TIER1_PATTERNS = [
@@ -3504,7 +3570,6 @@ var init_assertion_classifier = __esm({
3504
3570
  new RegExp(`\\.filter\\s*\\(\\s*\\{[^}]{0,200}?\\bhasText\\s*:\\s*${LOCATOR_ARG_VALUE}`, "gi")
3505
3571
  ];
3506
3572
  EXPECT_TOKEN = /\bexpect\s*[.(]/;
3507
- REGEX_PRECEDER = /* @__PURE__ */ new Set(["(", ",", "=", ":", "[", "!", "&", "|", "?", "{", "}", ";", "+", "-", "*", "%", "<", ">", "~", "^"]);
3508
3573
  }
3509
3574
  });
3510
3575
 
@@ -3512,14 +3577,80 @@ var init_assertion_classifier = __esm({
3512
3577
  function isExpectCall(code) {
3513
3578
  return EXPECT_CALL_RE.test(code);
3514
3579
  }
3580
+ function specCallsExpect(code) {
3581
+ if (EXPECT_CALL_RE.test(code) || SPACED_EXPECT_CALL_RE.test(code) || CONFIGURE_IN_PLACE_RE.test(code))
3582
+ return true;
3583
+ const names = /* @__PURE__ */ new Set();
3584
+ for (const clause of code.matchAll(IMPORT_CLAUSE_RE)) {
3585
+ for (const alias of clause[1].matchAll(EXPECT_ALIAS_RE))
3586
+ names.add(alias[1]);
3587
+ }
3588
+ for (const decl of code.matchAll(DERIVED_EXPECT_DECL_RE)) {
3589
+ if (names.size >= MAX_DERIVED_EXPECT_NAMES)
3590
+ break;
3591
+ const base = decl[2];
3592
+ if (base === void 0 || base === "expect" || names.has(base))
3593
+ names.add(decl[1]);
3594
+ }
3595
+ names.delete("expect");
3596
+ if (names.size === 0)
3597
+ return false;
3598
+ const alts = [...names].map((n) => n.replace(/\$/g, "\\$")).join("|");
3599
+ return new RegExp(String.raw`(?<![\w$.])(?:${alts})${EXPECT_MODIFIERS}`).test(code);
3600
+ }
3515
3601
  function asPlainExpect(code) {
3516
3602
  return code.replace(/\bexpect\.soft(\s*\()/g, "expect$1");
3517
3603
  }
3518
- var EXPECT_CALL_RE;
3604
+ function expectAliasNames(code) {
3605
+ if (!MAY_ALIAS.test(code))
3606
+ return [];
3607
+ const imported = [];
3608
+ for (const m of code.matchAll(PW_IMPORT)) {
3609
+ for (const a of m[1].matchAll(IMPORT_AS_EXPECT))
3610
+ imported.push(a[1]);
3611
+ }
3612
+ const known = /* @__PURE__ */ new Set(["expect", ...imported]);
3613
+ const derived = [];
3614
+ for (const m of code.matchAll(DECLARATION)) {
3615
+ const rhs = DERIVED.exec(code.slice(m.index + m[0].length, m.index + m[0].length + 200));
3616
+ if (!rhs)
3617
+ continue;
3618
+ const base = rhs[1];
3619
+ const fromKnown = base === void 0 || (rhs[2] ? base === "test" : known.has(base));
3620
+ if (!fromKnown || m[1] === "expect")
3621
+ continue;
3622
+ known.add(m[1]);
3623
+ derived.push(m[1]);
3624
+ }
3625
+ const fromImports = LOCAL_EXPECT.test(code) ? [] : imported;
3626
+ return [.../* @__PURE__ */ new Set([...fromImports, ...derived])].filter((n) => n !== "expect");
3627
+ }
3628
+ function withExpectAliasesAsExpect(code) {
3629
+ let out = code;
3630
+ for (const name of expectAliasNames(code)) {
3631
+ const escaped = name.replace(/\$/g, "\\$");
3632
+ out = out.replace(new RegExp(`(?<![\\w$.])${escaped}(?=\\s{0,20}(?:\\.\\s{0,20}(?:soft|poll)\\s{0,20})?\\()`, "g"), "expect");
3633
+ }
3634
+ return out;
3635
+ }
3636
+ var EXPECT_CALL_RE, EXPECT_MODIFIERS, SPACED_EXPECT_CALL_RE, CONFIGURE_IN_PLACE_RE, IMPORT_CLAUSE_RE, EXPECT_ALIAS_RE, DERIVED_EXPECT_DECL_RE, MAX_DERIVED_EXPECT_NAMES, PW_IMPORT, IMPORT_AS_EXPECT, DECLARATION, DERIVED, LOCAL_EXPECT, MAY_ALIAS;
3519
3637
  var init_expect_call = __esm({
3520
3638
  "../shared/dist/expect-call.js"() {
3521
3639
  "use strict";
3522
3640
  EXPECT_CALL_RE = /\bexpect(?:\.(?:soft|poll))?\s*\(/;
3641
+ EXPECT_MODIFIERS = String.raw`\s*(?:\.\s*soft\s*)?(?:\.\s*poll\s*)?\(`;
3642
+ SPACED_EXPECT_CALL_RE = new RegExp(String.raw`\bexpect${EXPECT_MODIFIERS}`);
3643
+ CONFIGURE_IN_PLACE_RE = new RegExp(String.raw`\bexpect\s*\.\s*configure\s*\(\s*(?:\{[^{}()]{0,500}\}\s*)?\)${EXPECT_MODIFIERS}`);
3644
+ IMPORT_CLAUSE_RE = /\bimport\s+(?:type\s+)?\{([^{}]{0,2000})\}/g;
3645
+ EXPECT_ALIAS_RE = /\bexpect\s+as\s+([A-Za-z_$][\w$]*)/g;
3646
+ DERIVED_EXPECT_DECL_RE = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:([A-Za-z_$][\w$]*)\s*\.\s*(?:configure|extend)|mergeExpects)\s*\(/g;
3647
+ MAX_DERIVED_EXPECT_NAMES = 20;
3648
+ PW_IMPORT = /\bimport\s{0,20}\{([^}]{0,4000})\}\s{0,20}from\s{0,20}['"]@playwright\/test['"]/g;
3649
+ IMPORT_AS_EXPECT = /(?:^|,)\s{0,20}expect\s{1,20}as\s{1,20}([A-Za-z_$][\w$]{0,99})\s{0,20}(?=,|$)/g;
3650
+ DECLARATION = /\b(?:const|let|var)\s{1,20}([A-Za-z_$][\w$]{0,99})\s{0,20}(?::[^=;\n]{0,200})?=\s{0,20}/g;
3651
+ DERIVED = /^(?:mergeExpects\s{0,20}\(|([A-Za-z_$][\w$]{0,99})(\s{0,20}\.\s{0,20}expect)?\s{0,20}\.\s{0,20}(?:configure|extend)\s{0,20}\(|test\s{0,20}\.\s{0,20}expect\b(?!\s{0,20}[.(]))/;
3652
+ LOCAL_EXPECT = /\b(?:const|let|var)\s{1,20}expect\b/;
3653
+ MAY_ALIAS = /\bexpect\s{1,20}as\b|\.\s{0,20}(?:configure|extend)\s{0,20}\(|\bmergeExpects\b|\btest\s{0,20}\.\s{0,20}expect\b/;
3523
3654
  }
3524
3655
  });
3525
3656
 
@@ -3549,65 +3680,6 @@ var init_braced_clause = __esm({
3549
3680
  }
3550
3681
  });
3551
3682
 
3552
- // ../shared/dist/regex-literal.js
3553
- function opensRegexLiteral(code, i) {
3554
- let j = i - 1;
3555
- while (j >= 0 && (code[j] === " " || code[j] === " "))
3556
- j--;
3557
- if (j < 0 || code[j] === "\n" || code[j] === "\r")
3558
- return true;
3559
- return regexOpensAfter(code, j);
3560
- }
3561
- function regexOpensAfter(code, j) {
3562
- const prev = code[j];
3563
- if (REGEX_OPENERS.includes(prev))
3564
- return !((prev === "+" || prev === "-") && code[j - 1] === prev);
3565
- if (!/[a-z]/.test(prev))
3566
- return false;
3567
- let start = j;
3568
- while (start > 0 && /[A-Za-z0-9_$]/.test(code[start - 1]))
3569
- start--;
3570
- if (start > 0 && code[start - 1] === ".")
3571
- return false;
3572
- return REGEX_KEYWORDS.has(code.slice(start, j + 1));
3573
- }
3574
- function regexLiteralEnd2(code, i) {
3575
- let k = i + 1;
3576
- let inClass = false;
3577
- while (k < code.length) {
3578
- const ch = code[k];
3579
- if (ch === "\n" || ch === "\r")
3580
- return k;
3581
- if (ch === "\\") {
3582
- if (code[k + 1] === "\n" || code[k + 1] === "\r")
3583
- return k + 1;
3584
- k += 2;
3585
- continue;
3586
- }
3587
- if (inClass) {
3588
- if (ch === "]")
3589
- inClass = false;
3590
- } else if (ch === "[") {
3591
- inClass = true;
3592
- } else if (ch === "/") {
3593
- k++;
3594
- while (k < code.length && /[a-z]/i.test(code[k]))
3595
- k++;
3596
- return k;
3597
- }
3598
- k++;
3599
- }
3600
- return code.length;
3601
- }
3602
- var REGEX_OPENERS, REGEX_KEYWORDS;
3603
- var init_regex_literal = __esm({
3604
- "../shared/dist/regex-literal.js"() {
3605
- "use strict";
3606
- REGEX_OPENERS = "(,=:[!&|?{};+-*%~^<>";
3607
- REGEX_KEYWORDS = /* @__PURE__ */ new Set(["return", "typeof", "case", "do", "else", "in", "of", "new", "delete", "void", "throw", "await", "yield", "instanceof"]);
3608
- }
3609
- });
3610
-
3611
3683
  // ../shared/dist/assertion-proof.js
3612
3684
  function fragmentKindOf(literal, fromText) {
3613
3685
  const text = literal.trim();
@@ -3766,7 +3838,7 @@ function stripCoverageComments(code) {
3766
3838
  let copyFrom = 0;
3767
3839
  let i = 0;
3768
3840
  let quote = null;
3769
- let prev = "";
3841
+ let prevAt = -1;
3770
3842
  let noRegexBefore = 0;
3771
3843
  while (i < code.length) {
3772
3844
  const ch = code[i];
@@ -3777,17 +3849,18 @@ function stripCoverageComments(code) {
3777
3849
  }
3778
3850
  if (ch === quote || ch === "\n" && quote !== "`") {
3779
3851
  quote = null;
3780
- prev = ch === "\n" ? prev : ch;
3852
+ if (ch !== "\n")
3853
+ prevAt = i;
3781
3854
  }
3782
3855
  i++;
3783
3856
  continue;
3784
3857
  }
3785
3858
  const next = code[i + 1];
3786
- if (ch === "/" && next !== "*" && next !== "/" && i >= noRegexBefore && (prev === "" || REGEX_PRECEDER2.includes(prev))) {
3859
+ if (ch === "/" && next !== "*" && next !== "/" && i >= noRegexBefore && (prevAt < 0 || regexOpensAfter(code, prevAt))) {
3787
3860
  const end = regexLiteralEnd3(code, i);
3788
3861
  if (end > 0) {
3789
3862
  i = end;
3790
- prev = "a";
3863
+ prevAt = end - 1;
3791
3864
  continue;
3792
3865
  }
3793
3866
  const eol = code.indexOf("\n", i);
@@ -3812,7 +3885,7 @@ function stripCoverageComments(code) {
3812
3885
  if (ch === '"' || ch === "'" || ch === "`")
3813
3886
  quote = ch;
3814
3887
  if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r")
3815
- prev = ch;
3888
+ prevAt = i;
3816
3889
  i++;
3817
3890
  }
3818
3891
  out.push(code.slice(copyFrom));
@@ -4501,11 +4574,12 @@ function assessTitleCoverage(title, code) {
4501
4574
  const repeatedHit = [...repeated].some((term) => hay.includes(term));
4502
4575
  return { covered: hit >= needed || repeatedHit, missingTerms };
4503
4576
  }
4504
- var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, REGEX_PRECEDER2, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
4577
+ var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
4505
4578
  var init_expected_outcome_coverage = __esm({
4506
4579
  "../shared/dist/expected-outcome-coverage.js"() {
4507
4580
  "use strict";
4508
4581
  init_assertion_classifier();
4582
+ init_regex_literal();
4509
4583
  COVERAGE_STOP_WORDS = /* @__PURE__ */ new Set([
4510
4584
  "after",
4511
4585
  "before",
@@ -4538,7 +4612,6 @@ var init_expected_outcome_coverage = __esm({
4538
4612
  ]);
4539
4613
  MAX_ASSERTION_JOIN_CHARS = 8e3;
4540
4614
  MAX_COVERAGE_LINE_CHARS = 2e3;
4541
- REGEX_PRECEDER2 = "(,=:[!&|?{};+-*%<>~^";
4542
4615
  BLOCK_SCAFFOLD = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
4543
4616
  cachedAssertionInput = null;
4544
4617
  cachedAssertionSource = { raw: "", normalized: "", bindings: /* @__PURE__ */ new Map(), bindingRefs: /* @__PURE__ */ new Map() };
@@ -4634,7 +4707,7 @@ function replaceBlockComments(code, replacer) {
4634
4707
  continue;
4635
4708
  const next = code[i + 1];
4636
4709
  if (next !== "/" && next !== "*" && opensRegexLiteral(code, i)) {
4637
- i = regexLiteralEnd2(code, i) - 1;
4710
+ i = regexLiteralEnd(code, i) - 1;
4638
4711
  continue;
4639
4712
  }
4640
4713
  if (next === "/" && !(code[i - 1] === ":" && /[A-Za-z]/.test(code[i - 2] ?? ""))) {
@@ -4809,10 +4882,41 @@ var init_auth_handoff_coverage = __esm({
4809
4882
  });
4810
4883
 
4811
4884
  // ../shared/dist/auth-plan.js
4812
- function hasInlineLoginSteps(playwrightCode) {
4885
+ function stripStampedHelperRegions(code) {
4886
+ let out = code;
4887
+ for (const [open, close] of STAMPED_HELPER_REGIONS) {
4888
+ if (out.indexOf(open) === -1)
4889
+ continue;
4890
+ let kept = "";
4891
+ let i = 0;
4892
+ while (i < out.length) {
4893
+ const start = out.indexOf(open, i);
4894
+ if (start === -1) {
4895
+ kept += out.slice(i);
4896
+ break;
4897
+ }
4898
+ const end = out.indexOf(close, start + open.length);
4899
+ if (end === -1) {
4900
+ kept += out.slice(i);
4901
+ break;
4902
+ }
4903
+ const lineEnd = out.indexOf("\n", end);
4904
+ kept += out.slice(i, start);
4905
+ i = lineEnd === -1 ? out.length : lineEnd + 1;
4906
+ }
4907
+ out = kept;
4908
+ }
4909
+ return out;
4910
+ }
4911
+ function signsInInline(playwrightCode) {
4813
4912
  if (!playwrightCode)
4814
4913
  return false;
4815
- return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
4914
+ if (!/process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode))
4915
+ return false;
4916
+ if (/process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode))
4917
+ return true;
4918
+ const body = stripStampedHelperRegions(playwrightCode);
4919
+ return /process\.env\.TEST_USER_EMAIL/.test(body) && /__vqaWaitForEmail\s*\(/.test(body);
4816
4920
  }
4817
4921
  function hasGeneratedAuthGuard(playwrightCode) {
4818
4922
  if (!playwrightCode)
@@ -4831,7 +4935,7 @@ function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
4831
4935
  if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
4832
4936
  return true;
4833
4937
  const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
4834
- const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
4938
+ const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && signsInInline(code);
4835
4939
  return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
4836
4940
  }
4837
4941
  function codeReadsStoredCredentials(code) {
@@ -4952,7 +5056,7 @@ function authTypeTag(tags) {
4952
5056
  const explicit = explicitAuthTypeTags(t);
4953
5057
  return explicit.length === 1 ? explicit[0] : void 0;
4954
5058
  }
4955
- var AUTH_TAG_COOKIE_STATE, AUTH_TAG_LOGIN_COOKIE, AUTH_TAG_LOGIN_BEARER, AUTH_TAG_STANDING, AUTH_TAG_NONE, AUTH_TAG_UNSUPPORTED, AUTH_TYPE_TAGS, AUTH_TAG_ALIASES, TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY;
5059
+ 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, STAMPED_HELPER_REGIONS;
4956
5060
  var init_auth_plan = __esm({
4957
5061
  "../shared/dist/auth-plan.js"() {
4958
5062
  "use strict";
@@ -4979,6 +5083,10 @@ var init_auth_plan = __esm({
4979
5083
  };
4980
5084
  TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
4981
5085
  API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
5086
+ STAMPED_HELPER_REGIONS = [
5087
+ ["// >>> VALIDATE_QA_INBOX", "// <<< VALIDATE_QA_INBOX"],
5088
+ ["// >>> VALIDATE_QA_SMS", "// <<< VALIDATE_QA_SMS"]
5089
+ ];
4982
5090
  }
4983
5091
  });
4984
5092
 
@@ -4987,6 +5095,9 @@ function parseLastActionProofMode(raw) {
4987
5095
  const v = (raw ?? "").trim().toLowerCase();
4988
5096
  return v === "on" || v === "shadow" ? v : "off";
4989
5097
  }
5098
+ function parseAbsenceProofMode(raw) {
5099
+ return parseLastActionProofMode(raw);
5100
+ }
4990
5101
  function isUrlProof(stmt) {
4991
5102
  if (URL_PROOF.test(stmt))
4992
5103
  return true;
@@ -5374,6 +5485,65 @@ function isVacuousAccountAbsence(stmt, shown, account) {
5374
5485
  }
5375
5486
  return true;
5376
5487
  }
5488
+ function isAbsenceCheck(stmt) {
5489
+ if (!claimsAbsence(stmt))
5490
+ return false;
5491
+ const bare = stmt.replace(NOT_BEFORE_MATCHER, ".");
5492
+ return ABSENCE_READ.test(bare) || VALUE_ABSENCE.test(bare) || OPTION_ABSENCE.test(bare);
5493
+ }
5494
+ function comparableSubject(subject) {
5495
+ let s = subject.replace(/^\s*await\s+/, "").replace(/\s+/g, "");
5496
+ for (let prev = ""; prev !== s; ) {
5497
+ prev = s;
5498
+ s = s.replace(/\.(?:count|isVisible|isHidden|first|last)\(\)$/, "").replace(/\.nth\(\d+\)$/, "");
5499
+ }
5500
+ return s;
5501
+ }
5502
+ function subjectsThatExisted(checks, interactionStmts) {
5503
+ const out = [];
5504
+ const bound = locatorBindings(interactionStmts);
5505
+ const resolve = (expr) => {
5506
+ for (let depth = 0; depth < 4; depth++) {
5507
+ const lead = expr.match(/^\s*(?:await\s+)?([A-Za-z_$][\w$]*)(?![\w$(])/);
5508
+ const to = lead ? bound.get(lead[1]) : void 0;
5509
+ if (!lead || to === void 0)
5510
+ break;
5511
+ expr = `${to.replace(/;\s*$/, "")}${expr.slice(lead[0].length)}`;
5512
+ }
5513
+ return expr;
5514
+ };
5515
+ for (const { stmt } of checks) {
5516
+ if (isAbsenceCheck(stmt))
5517
+ continue;
5518
+ const subject = extractExpectSubject(stmt);
5519
+ if (subject)
5520
+ out.push(comparableSubject(subject));
5521
+ }
5522
+ for (const stmt of interactionStmts) {
5523
+ if (EXPECT_CALL_RE.test(stmt))
5524
+ continue;
5525
+ for (const m of stmt.matchAll(LOCATOR_USE)) {
5526
+ if (m[0].includes("waitFor") && /state\s*:\s*['"`](?:hidden|detached)/.test(stmt.slice(m.index)))
5527
+ continue;
5528
+ const before = stmt.slice(0, m.index).replace(/^\s*(?:(?:const|let|var)\s+[\w$]+\s*=|return\b)\s*/, "");
5529
+ if (before.trim())
5530
+ out.push(comparableSubject(resolve(before)));
5531
+ }
5532
+ }
5533
+ return out;
5534
+ }
5535
+ function isUnshownAbsence(stmt, existed, entered) {
5536
+ if (!isAbsenceCheck(stmt))
5537
+ return false;
5538
+ const raw = extractExpectSubject(stmt);
5539
+ if (!raw || !/\b(?:locator|getBy[A-Z]\w*|frameLocator)\s*\(/.test(raw))
5540
+ return false;
5541
+ const subject = comparableSubject(raw);
5542
+ if (existed.some((e) => e === subject || e.startsWith(`${subject}.`)))
5543
+ return false;
5544
+ const named = raw.match(/['"`][^'"`]+['"`]/g) ?? [];
5545
+ return !named.some((q) => entered.values.has(q.slice(1, -1).toLowerCase()));
5546
+ }
5377
5547
  function endOfEnclosingCall(stmt, from) {
5378
5548
  let depth = 0;
5379
5549
  for (let i = from; i < stmt.length; i++) {
@@ -5873,10 +6043,31 @@ function comparesWithItself(subject, tail) {
5873
6043
  const expected = squash2(args);
5874
6044
  return expected === squash2(m[1] === "toHaveCount" ? `${subject}.count()` : subject);
5875
6045
  }
6046
+ function ariaSnapshotTexts(template) {
6047
+ const out = [];
6048
+ for (const raw of template.split(ARIA_ENTRY_SPLIT)) {
6049
+ const m = ARIA_ENTRY.exec(raw.trim());
6050
+ if (!m || !ARIA_CONTENT_ROLES.has(m[1]))
6051
+ continue;
6052
+ for (const text of [m[2], m[3]]) {
6053
+ const t = text?.replace(/\\(.)/g, "$1").replace(/^["']|["']$/g, "").trim();
6054
+ if (!t || t.startsWith("/") || !/[\p{L}\p{N}]/u.test(t) || /["'`\\]/.test(t))
6055
+ continue;
6056
+ out.push(t);
6057
+ }
6058
+ }
6059
+ return out;
6060
+ }
6061
+ function presenceTwin(stmt) {
6062
+ return stmt.replace(NOT_HIDDEN, ".toBeVisible(").replace(NOT_COUNT_ZERO, ".toBeVisible(");
6063
+ }
5876
6064
  function asPlainAssertion(s) {
5877
6065
  return plainAssertion(s, false);
5878
6066
  }
5879
6067
  function plainAssertion(s, keepSelfCompare) {
6068
+ const twin = presenceTwin(s.stmt);
6069
+ if (twin !== s.stmt)
6070
+ return plainAssertion({ ...s, stmt: twin }, keepSelfCompare);
5880
6071
  const { stmt } = s;
5881
6072
  if (COUNT_AT_LEAST_ZERO.test(stmt))
5882
6073
  return [];
@@ -5887,6 +6078,12 @@ function plainAssertion(s, keepSelfCompare) {
5887
6078
  const subjectAt = stmt.indexOf(subject, stmt.search(EXPECT_CALL_RE));
5888
6079
  const tail = (stmt.startsWith("expect.poll", stmt.search(EXPECT_CALL_RE)) ? expectCallTail(stmt) : null) ?? stmt.slice(subjectAt + subject.length).trimStart();
5889
6080
  const as = (rewritten) => [{ ...s, stmt: rewritten }];
6081
+ const aria = ARIA_TEMPLATE_CALL.exec(tail);
6082
+ if (aria && !NEGATED_ASSERT.test(stmt)) {
6083
+ const texts = ariaSnapshotTexts(aria[2]);
6084
+ if (texts.length > 0)
6085
+ return texts.map((t) => ({ ...s, stmt: `await expect(${plain}).toContainText(${JSON.stringify(t)});` }));
6086
+ }
5890
6087
  if (LOCATOR_EXPR.test(plain) && !LOCATOR_VALUE_READ.test(plain) && ALWAYS_TRUE_ON_OBJECT.test(tail))
5891
6088
  return [];
5892
6089
  if (DEFINED_VALUE_READ.test(plain) && ALWAYS_DEFINED.test(tail))
@@ -6193,7 +6390,8 @@ function teardownLines(lines) {
6193
6390
  }
6194
6391
  return out;
6195
6392
  }
6196
- function validateAssertionStrength(code, opts = {}) {
6393
+ function validateAssertionStrength(source, opts = {}) {
6394
+ const code = withExpectAliasesAsExpect(source);
6197
6395
  const body = stripLoginScaffolding(code);
6198
6396
  const lines = body.split("\n");
6199
6397
  const stmts = liveExpectStatements(stripCoverageComments(body)).flatMap(asPlainAssertion);
@@ -6346,8 +6544,23 @@ function validateAssertionStrength(code, opts = {}) {
6346
6544
  const orderedPersistence = firstMutLine !== void 0 ? persistence : void 0;
6347
6545
  const signedIn = signInProofWindow(interactionStmts, interactionStmtsWithLines, lines, blockIds);
6348
6546
  const proof = classifyOutcomeProof(checks, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence, signedIn);
6547
+ const absenceProof = opts.absenceProof ?? "off";
6548
+ let absenceOnly = false;
6549
+ if (absenceProof !== "off" && proof.kind === "durable") {
6550
+ const existed = subjectsThatExisted(checks, interactionStmtsRaw);
6551
+ const kept = checks.filter(({ stmt }) => !isUnshownAbsence(stmt, existed, entered));
6552
+ absenceOnly = kept.length < checks.length && classifyOutcomeProof(kept, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence, signedIn).kind !== "durable";
6553
+ if (absenceOnly && absenceProof === "on") {
6554
+ return {
6555
+ ok: false,
6556
+ severity: mutated ? "block" : "downgrade",
6557
+ reason: "the only outcome checks are absence checks (not visible / hidden / count 0) of something the test never showed, touched or typed: they pass whether or not the action worked \u2014 assert what the action produced (the new text, value, count or page), or assert the element present before it goes",
6558
+ signals: base({ interacted, mutated, navOnly, hasOutcomeProof: false, terminal, absenceOnly: true })
6559
+ };
6560
+ }
6561
+ }
6349
6562
  if (proof.kind === "durable") {
6350
- const signals2 = base({ interacted, mutated, navOnly, hasOutcomeProof: true, terminal });
6563
+ const signals2 = base({ interacted, mutated, navOnly, hasOutcomeProof: true, terminal, ...absenceOnly ? { absenceOnly } : {} });
6351
6564
  const { last, prev } = acts;
6352
6565
  if (teardown !== null && last !== null && acts.count >= 2 && last.kind !== "fill" && (last.kind === "enter" || isCommitClick(interactionStmts[last.i]) && !(prev?.kind === "click" && isDestructiveClick(interactionStmts[prev.i])))) {
6353
6566
  const lastLine = interactionStmtsWithLines[last.i].startLine;
@@ -6451,7 +6664,7 @@ function validateAssertionStrength(code, opts = {}) {
6451
6664
  signals
6452
6665
  };
6453
6666
  }
6454
- var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, POLL_OPEN, POLL_PAGE_URL, POLL_PAGE_URL_ONLY, POLL_CALLS_MAX, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, NEGATED_ASSERT_NOT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, VALUE_ABSENCE, OPTION_ABSENCE, NOT_BEFORE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, SCOPING_STEP, NEGATED_TEXT, ASYNC_DESTRUCTURE_OPEN, REQUEST_WORD, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, ENV_READ, ENTERED_ENV, HAVE_VALUE_ENV, ACCOUNT_KEYS, ACCOUNT_ENV, ACCOUNT_CONST, IN_FLIGHT_TEXT, LOCATOR_VALUE_READ, LOCATOR_EXPR, ALWAYS_TRUE_ON_OBJECT, COUNT_AT_LEAST_ZERO, DEFINED_VALUE_READ, ALWAYS_DEFINED, SELF_COMPARE_MATCHER, IS_VISIBLE_TRUE, PAGE_VALUE_MATCHER, COUNT_ONE, URL_CHECK_ARG, PLAIN_PAGE_PATH, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, ACCOUNT_EMAIL_ENV, ENV_PASSWORD_ENTRY, ACCOUNT_CONTROL_ROLE, ASSERT_OR_WAIT, ACTS, actLinesOf, CLOSERS_ONLY, TEST_BODY_OPEN, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD, COMMIT_CONTROL, NOT_COMMIT_CONTROL, DESTRUCTIVE_CONTROL, ENTER_PRESS, CONTROL_LABEL, isCommitClick, isDestructiveClick, TEARDOWN_OPEN;
6667
+ 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, ABSENCE_READ, LOCATOR_USE, 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, ARIA_TEMPLATE_CALL, ARIA_ENTRY_SPLIT, ARIA_CONTENT_ROLES, ARIA_ENTRY, NOT_HIDDEN, NOT_COUNT_ZERO, 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;
6455
6668
  var init_assertion_strength = __esm({
6456
6669
  "../shared/dist/assertion-strength.js"() {
6457
6670
  "use strict";
@@ -6540,6 +6753,8 @@ var init_assertion_strength = __esm({
6540
6753
  NOT_BEFORE_MATCHER = /\.not\s*\.\s*(?=to[A-Z])/g;
6541
6754
  ERROR_TEXT_WORDS = /error|fail|wrong|try again|unable|could ?n.?t|can.?t|problem|invalid|unavailable/i;
6542
6755
  TEXT_REGEX_ARG = /(?:getByText|hasText|getByRole\([^)]{0,200}name)\s*[:(]\s*(\/(?:\\.|[^/\\\n])+\/[a-z]*)/;
6756
+ ABSENCE_READ = /\.toBeVisible\s*\(|\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)|\.toBeAttached\s*\(|\.toHaveText\s*\(|\.toContainText\s*\(/;
6757
+ LOCATOR_USE = /\.\s*(?:click|dblclick|tap|hover|focus|fill|clear|press|pressSequentially|type|check|uncheck|setChecked|selectOption|setInputFiles|dragTo|selectText|waitFor)\s*\(/g;
6543
6758
  SCOPING_STEP = /(?:\s*\.\s*(?:first|last|nth)\s*\([^()]*\))*\s*\.\s*(?:getBy[A-Z]\w*|locator|filter)\s*\(/y;
6544
6759
  NEGATED_TEXT = /\.not\s*\.\s*(?:toHaveText|toContainText)\s*\(\s*(?:['"`]([^'"`]*)['"`]|\/((?:\\.|[^/\\\n])+)\/)?/;
6545
6760
  ASYNC_DESTRUCTURE_OPEN = /async\s*\(\s*\{/g;
@@ -6581,6 +6796,29 @@ var init_assertion_strength = __esm({
6581
6796
  IS_VISIBLE_TRUE = /^\)*\s*\.\s*(?:toBe\s*\(\s*true\s*\)|toBeTruthy\s*\(\s*\))/;
6582
6797
  PAGE_VALUE_MATCHER = /^\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
6583
6798
  COUNT_ONE = /\.\s*toHaveCount\s*\(\s*1\s*\)/;
6799
+ ARIA_TEMPLATE_CALL = /^\)\s*\.\s*toMatchAriaSnapshot\s*\(\s*(['"`])((?:\\[\s\S]|(?!\1)[^\\])*)\1/;
6800
+ ARIA_ENTRY_SPLIT = /(?:^|\s)-\s+(?=\/?[a-z][a-z-]*(?:\s|:|\[|$))/;
6801
+ ARIA_CONTENT_ROLES = /* @__PURE__ */ new Set([
6802
+ "listitem",
6803
+ "row",
6804
+ "cell",
6805
+ "gridcell",
6806
+ "article",
6807
+ "treeitem",
6808
+ "paragraph",
6809
+ "text",
6810
+ "strong",
6811
+ "emphasis",
6812
+ "code",
6813
+ "blockquote",
6814
+ "term",
6815
+ "definition",
6816
+ "alert",
6817
+ "status"
6818
+ ]);
6819
+ ARIA_ENTRY = /^([a-z][a-z-]*)(?:\s+"((?:\\.|[^"\\])*)")?(?:\s*\[[^\]]*\])*\s*(?::\s*(\S.*)?)?$/s;
6820
+ NOT_HIDDEN = /\.not\s*\.\s*toBeHidden\s*\(/g;
6821
+ NOT_COUNT_ZERO = /\.not\s*\.\s*toHaveCount\s*\(\s*0\s*(?:,\s*|(?=\)))/g;
6584
6822
  URL_CHECK_ARG = /\bpage\s*\.\s*waitForURL\s*\(\s*([^,)]+)|\bexpect(?:\s*\.\s*soft)?\s*\(\s*page\s*\)\s*\.\s*toHaveURL\s*\(\s*([^,)]+)/;
6585
6823
  PLAIN_PAGE_PATH = /^\/[\w\-./~%]*(?:[?#][^\s]*)?$/;
6586
6824
  FORM_CONTROL_LOCATOR = /getByRole\s*\(\s*['"`](?:combobox|switch|checkbox|radio|textbox|searchbox|spinbutton|slider)['"`]|getByLabel\s*\(|getByPlaceholder\s*\(/;
@@ -6626,6 +6864,12 @@ function isCancelledReason(error) {
6626
6864
  function isHeldHealRun(run) {
6627
6865
  return run?.status === "PASSED" && typeof run.error === "string" && run.error.startsWith(HEAL_PATCH_HELD_RUN_ERROR_PREFIX);
6628
6866
  }
6867
+ function mayBeUnverifiedHealPass(run, tags) {
6868
+ return !!run && run.status === "PASSED" && run.mode === "heal" && !isHeldHealRun(run) && (tags ?? []).includes(NEEDS_REVIEW_TAG);
6869
+ }
6870
+ function isUnverifiedHealPass(run, tags, healConfidence) {
6871
+ return mayBeUnverifiedHealPass(run, tags) && typeof healConfidence === "string" && UNVERIFIED_HEAL_CONFIDENCES.has(healConfidence);
6872
+ }
6629
6873
  function isRunInterruptedError(error) {
6630
6874
  return typeof error === "string" && RUN_INTERRUPTED_ERROR_PREFIXES.some((prefix) => error.startsWith(prefix));
6631
6875
  }
@@ -6712,12 +6956,25 @@ function classifyErrorMessage(error) {
6712
6956
  return "config_issue";
6713
6957
  if (EMAIL_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
6714
6958
  return "email_not_received";
6959
+ if (SMS_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
6960
+ return "email_not_received";
6715
6961
  if (isCancelledReason(error))
6716
6962
  return "cancelled";
6717
6963
  if (isRetriedRunInterruption(error))
6718
6964
  return "interrupted";
6719
6965
  return null;
6720
6966
  }
6967
+ function isSmsNotReceivedError(error) {
6968
+ if (typeof error !== "string")
6969
+ return false;
6970
+ const normalized = error.replace(/^\s+/, "").replace(/^Error:\s*/i, "");
6971
+ return SMS_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix));
6972
+ }
6973
+ function blockedRunLogLine(error, durationMs) {
6974
+ const reason = classifyErrorMessage(error);
6975
+ const tail = reason === "rate_limit" ? "rate-limit detected, not counted as failure" : reason === "config_issue" ? "config issue (needs setup or sign-in), not counted as failure" : reason === "email_not_received" ? "verification email not received, not counted as failure" : reason === "cancelled" ? "cancelled, not counted as failure" : reason === "interrupted" ? "run interrupted, not counted as failure" : "run error";
6976
+ return `BLOCKED (${durationMs}ms) \u2014 ${tail}`;
6977
+ }
6721
6978
  function stripEnvironmentalPrefix(error) {
6722
6979
  for (const prefix of ENVIRONMENTAL_ERROR_PREFIXES) {
6723
6980
  if (error.startsWith(prefix)) {
@@ -6780,8 +7037,19 @@ function computePassRate(passed, tested) {
6780
7037
  }
6781
7038
  function summarizeHealth(items) {
6782
7039
  const c = { passed: 0, failed: 0, errored: 0, environmental: 0, never_run: 0 };
7040
+ let rateLimited = 0;
7041
+ let configIssue = 0;
6783
7042
  for (const item of items) {
6784
- c[classifyTestHealth(item.run)] += item.weight ?? 1;
7043
+ const weight = item.weight ?? 1;
7044
+ const bucket = classifyTestHealth(item.run);
7045
+ c[bucket] += weight;
7046
+ if (bucket === "environmental") {
7047
+ const reason = classifyErrorMessage(item.run?.error);
7048
+ if (reason === "rate_limit")
7049
+ rateLimited += weight;
7050
+ else if (isConfigLikeReason(reason))
7051
+ configIssue += weight;
7052
+ }
6785
7053
  }
6786
7054
  const tested = c.passed + c.failed + c.errored;
6787
7055
  return {
@@ -6789,6 +7057,8 @@ function summarizeHealth(items) {
6789
7057
  failed: c.failed,
6790
7058
  errored: c.errored,
6791
7059
  environmental: c.environmental,
7060
+ rateLimited,
7061
+ configIssue,
6792
7062
  neverRun: c.never_run,
6793
7063
  tested,
6794
7064
  total: tested + c.environmental + c.never_run,
@@ -6825,7 +7095,7 @@ function reportedBugWhere() {
6825
7095
  function bugReviewStatus(status) {
6826
7096
  return status === "QUARANTINED" ? "confirmed" : "needs-review";
6827
7097
  }
6828
- var USER_CANCELLED_RUN_ERROR, PLATFORM_SUPPORT_CANCELLED_RUN_ERROR, PLATFORM_ADMIN_CANCELLED_RUN_ERROR, ADMIN_FORCE_CANCELLED_RUN_ERROR, CHAIN_CANCELLED_RUN_ERROR, RETRY_HELD_RUN_ERROR, CANCELLED_REASON_PREFIXES, isUserCancelledError, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, TEST_DID_NOT_RUN_ERROR, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, FEATURE_ROW_RUN_WINDOW, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
7098
+ 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, UNVERIFIED_HEAL_CONFIDENCES, NEEDS_REVIEW_TAG, 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, SMS_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;
6829
7099
  var init_run_classification = __esm({
6830
7100
  "../shared/dist/run-classification.js"() {
6831
7101
  "use strict";
@@ -6845,6 +7115,8 @@ var init_run_classification = __esm({
6845
7115
  ];
6846
7116
  isUserCancelledError = isCancelledReason;
6847
7117
  HEAL_PATCH_HELD_RUN_ERROR_PREFIX = "Heal patch held";
7118
+ UNVERIFIED_HEAL_CONFIDENCES = /* @__PURE__ */ new Set(["low", "medium"]);
7119
+ NEEDS_REVIEW_TAG = "@needs-review";
6848
7120
  RUNNER_INTERRUPT_ERROR_PREFIX = "Runner shutting down";
6849
7121
  SERVER_INTERRUPT_ERROR_PREFIX = "Run interrupted by server restart";
6850
7122
  RUN_LOST_ERROR_PREFIX = "Runner lost this run";
@@ -6865,10 +7137,12 @@ var init_run_classification = __esm({
6865
7137
  "VERIFICATION_EMAIL_HAS_NO_CODE:",
6866
7138
  "VERIFICATION_EMAIL_HAS_NO_LINK:"
6867
7139
  ];
7140
+ SMS_NOT_RECEIVED_PREFIXES = ["VERIFICATION_SMS_NOT_RECEIVED:"];
6868
7141
  ENVIRONMENTAL_ERROR_PREFIXES = [
6869
7142
  "RATE_LIMIT_BLOCKED:",
6870
7143
  "CONFIG_ISSUE:",
6871
- ...EMAIL_NOT_RECEIVED_PREFIXES
7144
+ ...EMAIL_NOT_RECEIVED_PREFIXES,
7145
+ ...SMS_NOT_RECEIVED_PREFIXES
6872
7146
  ];
6873
7147
  ENVIRONMENTAL_ERROR_STORED_PREFIXES = [
6874
7148
  ...ENVIRONMENTAL_ERROR_PREFIXES.flatMap((prefix) => [prefix, `Error: ${prefix}`]),
@@ -9253,6 +9527,19 @@ function googleBrokerUnavailableLine(lines, testError) {
9253
9527
  }
9254
9528
  return last;
9255
9529
  }
9530
+ function googleSignInRefusedLine(lines) {
9531
+ let last = null;
9532
+ for (const raw of lines ?? []) {
9533
+ for (const line of String(raw).split("\n")) {
9534
+ const text = line.trim();
9535
+ if (GOOGLE_SIGN_IN_REFUSED.test(text))
9536
+ last = text;
9537
+ else if (GOOGLE_SIGNED_IN.test(text))
9538
+ last = null;
9539
+ }
9540
+ }
9541
+ return last;
9542
+ }
9256
9543
  function stepConsoleLines(result) {
9257
9544
  const steps = Array.isArray(result) ? result : result && typeof result === "object" && Array.isArray(result.stepResults) ? result.stepResults : [];
9258
9545
  return steps.flatMap((step) => {
@@ -9260,7 +9547,23 @@ function stepConsoleLines(result) {
9260
9547
  return Array.isArray(logs) ? logs.filter((line) => typeof line === "string") : [];
9261
9548
  });
9262
9549
  }
9263
- var GOOGLE_BROKER_UNAVAILABLE, GOOGLE_BROKER_ASKED, GOOGLE_SIGNED_IN, TIMEOUT_ERROR;
9550
+ function googleSignInRefusedReason(lines) {
9551
+ let reason = null;
9552
+ for (const raw of lines ?? []) {
9553
+ for (const line of String(raw).split("\n")) {
9554
+ const text = line.trim();
9555
+ if (GOOGLE_SIGNED_IN.test(text)) {
9556
+ reason = null;
9557
+ continue;
9558
+ }
9559
+ const m = GOOGLE_SIGN_IN_ANSWER.exec(text);
9560
+ if (m)
9561
+ reason = m[1];
9562
+ }
9563
+ }
9564
+ return reason;
9565
+ }
9566
+ var GOOGLE_BROKER_UNAVAILABLE, GOOGLE_BROKER_ASKED, GOOGLE_SIGNED_IN, TIMEOUT_ERROR, GOOGLE_SIGN_IN_REFUSED, GOOGLE_SIGN_IN_ANSWER;
9264
9567
  var init_google_broker_lines = __esm({
9265
9568
  "../shared/dist/google-broker-lines.js"() {
9266
9569
  "use strict";
@@ -9268,6 +9571,8 @@ var init_google_broker_lines = __esm({
9268
9571
  GOOGLE_BROKER_ASKED = /^\[google-sign-in\] the app started Google sign-in; asking the broker/;
9269
9572
  GOOGLE_SIGNED_IN = /^\[google-sign-in\] returned to the app with Google sign-in/;
9270
9573
  TIMEOUT_ERROR = /Test timeout of \d+ms exceeded|TimeoutError|Timeout:? \d+ ?ms/i;
9574
+ GOOGLE_SIGN_IN_REFUSED = /^\[google-sign-in\] (?:not_seeded|not_enabled|expired|needs_reseed|device_approval|needs_verification|policy_rejected|unsupported_flow|unsupported_one_tap): /;
9575
+ GOOGLE_SIGN_IN_ANSWER = /^\[google-sign-in\] ([a-z][a-z0-9_]*): /;
9271
9576
  }
9272
9577
  });
9273
9578
 
@@ -9319,22 +9624,87 @@ function isIntentionalNegativeAuthTest(input) {
9319
9624
  const meta = [input.testName, input.description].filter(Boolean).join(" ");
9320
9625
  return matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH);
9321
9626
  }
9627
+ function signInStepPassedBeforeFailure(steps) {
9628
+ const list = steps ?? [];
9629
+ const failedAt = list.findIndex((step) => typeof step.status === "string" && step.status !== "passed" && step.status !== "skipped");
9630
+ if (failedAt <= 0)
9631
+ return false;
9632
+ if (SIGN_IN_STEP_TITLE.test(list[failedAt].title ?? ""))
9633
+ return false;
9634
+ return list.slice(0, failedAt).some((step) => step.status === "passed" && SIGN_IN_STEP_TITLE.test(step.title ?? ""));
9635
+ }
9636
+ function loginFormAlertTexts(body) {
9637
+ const text = body.length > ERROR_CONTEXT_CAP ? body.slice(0, ERROR_CONTEXT_CAP) : body;
9638
+ const blocks = [];
9639
+ let open = null;
9640
+ for (const line of text.split("\n")) {
9641
+ const fence = /^```(\w*)$/.exec(line.trim());
9642
+ if (fence) {
9643
+ if (open) {
9644
+ blocks.push(open);
9645
+ open = null;
9646
+ } else if (fence[1].toLowerCase() === "yaml")
9647
+ open = [];
9648
+ continue;
9649
+ }
9650
+ if (open)
9651
+ open.push(line);
9652
+ }
9653
+ const alerts = [];
9654
+ for (const lines of blocks) {
9655
+ if (!lines.some((l) => SNAPSHOT_PASSWORD_FIELD.test(l)))
9656
+ continue;
9657
+ for (let i = 0; i < lines.length && alerts.length < MAX_ALERTS; i++) {
9658
+ const m = SNAPSHOT_ALERT.exec(lines[i]);
9659
+ if (!m)
9660
+ continue;
9661
+ const indent = m[1].length;
9662
+ const parts = [m[2]];
9663
+ let j = i + 1;
9664
+ for (; j < lines.length; j++) {
9665
+ const lead = /^\s*/.exec(lines[j])?.[0].length ?? 0;
9666
+ if (lines[j].trim() === "" || lead <= indent)
9667
+ break;
9668
+ parts.push(lines[j].trim());
9669
+ }
9670
+ alerts.push(parts.join(" ").slice(0, MAX_ALERT_CHARS));
9671
+ i = j - 1;
9672
+ }
9673
+ }
9674
+ return alerts;
9675
+ }
9676
+ function stepLoginFormAlerts(steps) {
9677
+ const out = [];
9678
+ for (const sr of steps ?? []) {
9679
+ for (const a of sr.attachments ?? []) {
9680
+ if (a?.name === "error-context" && typeof a.body === "string")
9681
+ out.push(...loginFormAlertTexts(a.body));
9682
+ }
9683
+ }
9684
+ return out;
9685
+ }
9322
9686
  function detectAuthBlock(input) {
9323
9687
  const tags = input.tags ?? [];
9324
9688
  const isAuthed = tags.some((t) => AUTHED_TAGS.includes(t));
9325
- if (!isAuthed)
9689
+ const signsInInline2 = !isAuthed && codeReadsStoredCredentials(input.code);
9690
+ if (!isAuthed && !signsInInline2)
9326
9691
  return NO_DETECTION;
9327
9692
  if (isIntentionalNegativeAuthTest(input))
9328
9693
  return NO_DETECTION;
9694
+ if (signInStepPassedBeforeFailure(input.stepResults))
9695
+ return NO_DETECTION;
9329
9696
  const sources = [];
9330
- if (input.error)
9331
- sources.push(stripEchoLines(input.error));
9332
- if (input.logs)
9333
- sources.push(stripEchoLines(input.logs));
9334
- for (const sr of input.stepResults ?? []) {
9335
- if (sr.error)
9336
- sources.push(stripEchoLines(sr.error));
9697
+ if (isAuthed) {
9698
+ if (input.error)
9699
+ sources.push(stripEchoLines(input.error));
9700
+ if (input.logs)
9701
+ sources.push(stripEchoLines(input.logs));
9702
+ for (const sr of input.stepResults ?? []) {
9703
+ if (sr.error)
9704
+ sources.push(stripEchoLines(sr.error));
9705
+ }
9337
9706
  }
9707
+ sources.push(...stepLoginFormAlerts(input.stepResults));
9338
9708
  for (const text of sources) {
9339
9709
  if (!text)
9340
9710
  continue;
@@ -9383,10 +9753,11 @@ function detectApiColdSessionBlock(input) {
9383
9753
  function coldSessionConfigMessage(context, reason, evidence) {
9384
9754
  return `CONFIG_ISSUE: ${context} without valid injected auth (${reason}) \u2014 no session cookie was injected this run. Ensure the project login (auth-setup recipe / prewarm) is configured so the shared session jar warms (a project with no login: set TEST_USER_EMAIL and TEST_USER_PASSWORD in Test Data \u2192 Test Variables so a session can be captured); for endpoints that authenticate with a standing API key or bearer header instead, set them in the project's API_AUTH_HEADERS variable (injected as request headers). Evidence: ${evidence.join("; ")}`;
9385
9755
  }
9386
- var NO_DETECTION, AUTHED_TAGS, LOGIN_CTX, AUTH_BLOCK_SIGNATURES, INTENTIONAL_NEGATIVE_AUTH, ECHO_LINE_RE, UNAUTHORIZED_TEXT_RE;
9756
+ var NO_DETECTION, AUTHED_TAGS, LOGIN_CTX, AUTH_BLOCK_SIGNATURES, INTENTIONAL_NEGATIVE_AUTH, ECHO_LINE_RE, SIGN_IN_STEP_TITLE, ERROR_CONTEXT_CAP, MAX_ALERTS, MAX_ALERT_CHARS, SNAPSHOT_PASSWORD_FIELD, SNAPSHOT_ALERT, UNAUTHORIZED_TEXT_RE;
9387
9757
  var init_auth_block_detector = __esm({
9388
9758
  "../shared/dist/auth-block-detector.js"() {
9389
9759
  "use strict";
9760
+ init_auth_plan();
9390
9761
  NO_DETECTION = { detected: false, reason: "", evidence: [] };
9391
9762
  AUTHED_TAGS = ["@needs-auth", "@inline-login"];
9392
9763
  LOGIN_CTX = "\\b(?:log\\s*in|login|logging\\s+in|sign[\\s-]?in|signing\\s+in|continue)\\b";
@@ -9396,6 +9767,10 @@ var init_auth_block_detector = __esm({
9396
9767
  { reason: "invalid credentials", re: /\b(invalid|incorrect|wrong|bad)\s+(login\s+)?credentials\b/i },
9397
9768
  { reason: "credentials do not match", re: /\bcredentials\b[^\n]{0,40}\b(do(?:es)?\s*n['’]?t|do\s+not)\s+match\b/i },
9398
9769
  { reason: "incorrect email or password", re: /\b(incorrect|invalid|wrong)\s+(email|username)\s+or\s+password\b/i },
9770
+ { reason: "username and password do not match", re: /\b(?:user\s*name|e-?mail)\s+and\s+password\s+(?:do(?:es)?\s*n['’]?t|do(?:es)?\s+not)\s+match\b/i },
9771
+ // A locked account is a blocker of the same kind: the configured user can't sign in until it is unlocked. The
9772
+ // rate-limit detector keeps the timed lockouts ("temporarily locked", "too many attempts").
9773
+ { reason: "account locked out", re: /\b(?:user|account)\b[^\n]{0,30}\b(?:has\s+been|is)\s+locked\s+out\b/i },
9399
9774
  // Require an EXPLICIT negative (codex #21): the old `(not\s+)?(confirmed|verified)` made the negation
9400
9775
  // optional, so a benign "Email verified" success message / feature assertion matched and hid a real
9401
9776
  // feature failure as a CONFIG_ISSUE. Only "email not (yet) confirmed/verified" or "unconfirmed/unverified".
@@ -9427,6 +9802,12 @@ var init_auth_block_detector = __esm({
9427
9802
  /\b(reject|deny|denied|block(ed)?)\b[^\n]{0,20}\b(login|sign[\s-]?in|auth|credential|password)\b/i
9428
9803
  ];
9429
9804
  ECHO_LINE_RE = /^\s*(?:(?:📋|🎭|🌐|⚙️?)|(?:stderr:\s*|stdout:\s*)?Locator:)/u;
9805
+ SIGN_IN_STEP_TITLE = /\b(?:log(?:ging)?\s*in(?:to)?|sign(?:ing)?[\s-]?in(?:to)?|authenticat(?:e|es|ed|ing|ion))\b/i;
9806
+ ERROR_CONTEXT_CAP = 64 * 1024;
9807
+ MAX_ALERTS = 20;
9808
+ MAX_ALERT_CHARS = 1e3;
9809
+ SNAPSHOT_PASSWORD_FIELD = /^\s*- textbox "[^"\n]{0,80}\bpass(?:word|code)?\b/i;
9810
+ SNAPSHOT_ALERT = /^(\s*)- alert\b(.*)$/;
9430
9811
  UNAUTHORIZED_TEXT_RE = /(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|gives?|sends?|yield(?:s|ed|ing)?|results?\s+in)\s+(?:a\s+|an\s+|the\s+)?(?:401|403)\b|\bstatus(?:\s+code)?\s*(?::\s*)?(?:401|403)\b|\bHTTP\/[\d.]+\s+(?:401|403)\b|(?:401|403)['’]d\b|(?:[→⇒]|->)\s*(?:401|403)\b|(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|threw|throws?)\s+(?:a\s+|an\s+)?(?:unauthorized|forbidden)\b|\bnot\s+authenticated\b/i;
9431
9812
  }
9432
9813
  });
@@ -9861,6 +10242,77 @@ var init_redact_needles = __esm({
9861
10242
  }
9862
10243
  });
9863
10244
 
10245
+ // ../shared/dist/credential-header-scrub.js
10246
+ function scrubCredentialHeaders(text) {
10247
+ if (text.length < 6)
10248
+ return text;
10249
+ let out = text.replace(HEADER_LINE, (match, keyQuote, name, sep, value) => {
10250
+ if (value === "" || ALREADY_REDACTED.test(value))
10251
+ return match;
10252
+ if (COOKIE_NAME.test(name) && !value.includes("="))
10253
+ return match;
10254
+ if (AUTHORIZATION_NAME.test(name) && WORDS_ONLY.test(value))
10255
+ return match;
10256
+ const q = value[0] === '"' || value[0] === "'" ? value[0] : "";
10257
+ return `${keyQuote}${name}${keyQuote}${sep}${q}${REDACTED2}${q}`;
10258
+ });
10259
+ out = out.replace(SET_COOKIE_PAIR, `$1${REDACTED2}`);
10260
+ return out;
10261
+ }
10262
+ function isCredentialHeaderName(name) {
10263
+ return name.length <= 100 && HEADER_NAME_ONLY.test(name);
10264
+ }
10265
+ function scrubCredentialHeaderValue(value) {
10266
+ if (typeof value === "string")
10267
+ return value === REDACTED2 || value === "" ? value : REDACTED2;
10268
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
10269
+ const next = value.map((item) => item === REDACTED2 || item === "" ? item : REDACTED2);
10270
+ return next.every((item, i) => item === value[i]) ? value : next;
10271
+ }
10272
+ return value;
10273
+ }
10274
+ function scrubCredentialHeadersDeep(value) {
10275
+ if (typeof value === "string")
10276
+ return scrubCredentialHeaders(value);
10277
+ if (Array.isArray(value)) {
10278
+ let changed = false;
10279
+ const next = value.map((item) => {
10280
+ const scrubbed = scrubCredentialHeadersDeep(item);
10281
+ if (scrubbed !== item)
10282
+ changed = true;
10283
+ return scrubbed;
10284
+ });
10285
+ return changed ? next : value;
10286
+ }
10287
+ if (value !== null && typeof value === "object") {
10288
+ let changed = false;
10289
+ const next = {};
10290
+ for (const [k, v] of Object.entries(value)) {
10291
+ const scrubbed = isCredentialHeaderName(k) ? scrubCredentialHeaderValue(v) : scrubCredentialHeadersDeep(v);
10292
+ if (scrubbed !== v)
10293
+ changed = true;
10294
+ next[k] = scrubbed;
10295
+ }
10296
+ return changed ? next : value;
10297
+ }
10298
+ return value;
10299
+ }
10300
+ var REDACTED2, HEADER_NAMES, COOKIE_NAME, AUTHORIZATION_NAME, WORDS_ONLY, HEADER_NAME_ONLY, HEADER_LINE, ALREADY_REDACTED, SET_COOKIE_PAIR;
10301
+ var init_credential_header_scrub = __esm({
10302
+ "../shared/dist/credential-header-scrub.js"() {
10303
+ "use strict";
10304
+ REDACTED2 = "[REDACTED]";
10305
+ HEADER_NAMES = "cookie2?|set-cookie2?|proxy-authorization|authorization|api-?key|(?:x-)?(?:csrf|xsrf)[a-z0-9-]{0,30}|x-[a-z0-9-]{0,40}?(?:token|session|secret|key|auth)[a-z0-9-]{0,40}";
10306
+ COOKIE_NAME = /^(?:set-)?cookie2?$/i;
10307
+ AUTHORIZATION_NAME = /^(?:proxy-)?authorization$/i;
10308
+ WORDS_ONLY = /^[A-Za-z]+(?: [A-Za-z]+){0,3}$/;
10309
+ HEADER_NAME_ONLY = new RegExp(`^(?:${HEADER_NAMES})$`, "i");
10310
+ HEADER_LINE = new RegExp(`(?<![A-Za-z0-9_-])(["']?)(${HEADER_NAMES})\\1(\\s*:\\s*)("(?:[^"\\\\\\r\\n]|\\\\.)*"|'(?:[^'\\\\\\r\\n]|\\\\.)*'|[^\\r\\n\\x1b]*)`, "gi");
10311
+ ALREADY_REDACTED = /^["']?(?:[A-Za-z][\w-]*\s+)?\[REDACTED\]["']?[,;]?\s*$/;
10312
+ SET_COOKIE_PAIR = /(?<![A-Za-z0-9_-])([A-Za-z0-9_.%-]{1,64}=)(?!\[REDACTED\])([^\s;,"'<>]{6,2048})(?=;\s*(?:Path|Domain|Expires|Max-Age|HttpOnly|Secure|SameSite|Partitioned)\b)/gi;
10313
+ }
10314
+ });
10315
+
9864
10316
  // ../shared/dist/deepgram-voices.js
9865
10317
  function voice(family, name, gender, accent, description) {
9866
10318
  const label = name.charAt(0).toUpperCase() + name.slice(1);
@@ -10437,13 +10889,13 @@ function isSensitiveQueryKey(key) {
10437
10889
  return SENSITIVE_QUERY_KEY_RE.test(key);
10438
10890
  }
10439
10891
  function redactSecretValues(value) {
10440
- return value.replace(MCP_SECRET_VALUE_RE, REDACTED2);
10892
+ return value.replace(MCP_SECRET_VALUE_RE, REDACTED3);
10441
10893
  }
10442
10894
  function scrubReturnUrlValue(value, depth) {
10443
10895
  if (!/[?%]/.test(value))
10444
10896
  return value;
10445
10897
  if (depth >= MAX_NESTED_URL_DEPTH)
10446
- return REDACTED2;
10898
+ return REDACTED3;
10447
10899
  const q = value.indexOf("?");
10448
10900
  if (q !== -1) {
10449
10901
  const hashIdx = value.indexOf("#", q);
@@ -10465,7 +10917,7 @@ function scrubReturnUrlValue(value, depth) {
10465
10917
  try {
10466
10918
  return encodeURIComponent(scrubbed);
10467
10919
  } catch {
10468
- return REDACTED2;
10920
+ return REDACTED3;
10469
10921
  }
10470
10922
  }
10471
10923
  function scrubQuery(search, depth) {
@@ -10481,7 +10933,7 @@ function scrubQuery(search, depth) {
10481
10933
  params.forEach((value, key) => {
10482
10934
  let scrubbed = value;
10483
10935
  if (isSensitiveQueryKey(key)) {
10484
- scrubbed = REDACTED2;
10936
+ scrubbed = REDACTED3;
10485
10937
  } else {
10486
10938
  if (RETURN_URL_QUERY_KEY_RE.test(key))
10487
10939
  scrubbed = scrubReturnUrlValue(scrubbed, depth);
@@ -10593,12 +11045,12 @@ function scrubUrlPropertyBag(props) {
10593
11045
  } catch {
10594
11046
  }
10595
11047
  }
10596
- var SENSITIVE_QUERY_KEY_RE, REDACTED2, MCP_SECRET_VALUE_RE, RETURN_URL_QUERY_KEY_RE, MAX_NESTED_URL_DEPTH, MAX_PROPERTY_SWEEP_DEPTH;
11048
+ var SENSITIVE_QUERY_KEY_RE, REDACTED3, MCP_SECRET_VALUE_RE, RETURN_URL_QUERY_KEY_RE, MAX_NESTED_URL_DEPTH, MAX_PROPERTY_SWEEP_DEPTH;
10597
11049
  var init_analytics_url = __esm({
10598
11050
  "../shared/dist/analytics-url.js"() {
10599
11051
  "use strict";
10600
11052
  SENSITIVE_QUERY_KEY_RE = /^(access_token|refresh_token|id_token|token|code|state|password|secret|api[_-]?key|auth|authorization|session|jwt|otp|email|phone|magic[_-]?link)$|token|secret|password|credential|email|phone|(^|[_-])(otp|verification|auth)[_-]?code($|[_-])|(^|[_-])(state|session|grant)$/i;
10601
- REDACTED2 = "[redacted]";
11053
+ REDACTED3 = "[redacted]";
10602
11054
  MCP_SECRET_VALUE_RE = /mcp_(consent|code|at|rt)_[A-Za-z0-9]+/g;
10603
11055
  RETURN_URL_QUERY_KEY_RE = /^(next|redirect|return|continue)([_-]?(to|url|uri|path))?$/i;
10604
11056
  MAX_NESTED_URL_DEPTH = 5;
@@ -13428,8 +13880,19 @@ function successContradicted(text) {
13428
13880
  }
13429
13881
  return SUCCESS_NO_STATUS_RE.test(text) || OFF_BY_RE.test(text);
13430
13882
  }
13883
+ function negatedSafeguard(text) {
13884
+ for (const match of matches(NEGATED_SAFEGUARD_RE, text)) {
13885
+ if (!NOT_KEPT_RE.test(match[1]))
13886
+ return true;
13887
+ const head = text.slice(Math.max(0, match.index - SAFEGUARD_LOOKBEHIND), match.index);
13888
+ const sentence = head.slice(Math.max(head.lastIndexOf("."), head.lastIndexOf(";"), head.lastIndexOf("\n")) + 1);
13889
+ if (!REFUSED_INPUT_RE.test(sentence))
13890
+ return true;
13891
+ }
13892
+ return false;
13893
+ }
13431
13894
  function claimsDefect(text) {
13432
- if (BUG_LABEL_RE.test(text) || SILENT_RE.test(text) || CONCESSION_RE.test(text) || successContradicted(text) || NEGATED_SAFEGUARD_RE.test(text)) {
13895
+ if (BUG_LABEL_RE.test(text) || SILENT_RE.test(text) || CONCESSION_RE.test(text) || successContradicted(text) || negatedSafeguard(text)) {
13433
13896
  return true;
13434
13897
  }
13435
13898
  return BAD_VALUE_RE.test(text) && !REFUSAL_RE.test(text);
@@ -13437,7 +13900,7 @@ function claimsDefect(text) {
13437
13900
  function hasTestFailContract(code) {
13438
13901
  return TEST_FAIL_CONTRACT_RE.test(code);
13439
13902
  }
13440
- var BUG_LABEL_RE, SILENT_RE, CONCESSION_RE, SUCCESS_REPORT, SUCCESS_CONTRADICTED_RE, CONTRADICTION_VETO_RE, NOTHING_PROVIDED_RE, UNTOUCHED_SUBSETS, ACTION_LOOKBEHIND, TRAILING_ACTION_RE, ADJECTIVE_BEFORE_NOUN_RE, SUCCESS_SAME_STATE_RE, GENERIC_REPORT_RE, SAME_STATE, OTHER_ITEM_RE, SUCCESS_STATUS_RE, STATUS_ACTION_RE, SUCCESS_WITHOUT_EFFECT_RE, COUNT_UNCHANGED_RE, CLEARING_ACTION_RE, NOT_A_CLEARING_RE, SUCCESS_NO_STATUS_RE, OFF_BY_RE, NEGATED_SAFEGUARD_RE, BAD_VALUE_RE, REFUSAL_RE, TEST_FAIL_CONTRACT_RE;
13903
+ var BUG_LABEL_RE, SILENT_RE, CONCESSION_RE, SUCCESS_REPORT, SUCCESS_CONTRADICTED_RE, CONTRADICTION_VETO_RE, NOTHING_PROVIDED_RE, UNTOUCHED_SUBSETS, ACTION_LOOKBEHIND, TRAILING_ACTION_RE, ADJECTIVE_BEFORE_NOUN_RE, SUCCESS_SAME_STATE_RE, GENERIC_REPORT_RE, SAME_STATE, OTHER_ITEM_RE, SUCCESS_STATUS_RE, STATUS_ACTION_RE, SUCCESS_WITHOUT_EFFECT_RE, COUNT_UNCHANGED_RE, CLEARING_ACTION_RE, NOT_A_CLEARING_RE, SUCCESS_NO_STATUS_RE, OFF_BY_RE, NEGATED_SAFEGUARD_RE, NOT_KEPT_RE, REFUSED_INPUT_RE, SAFEGUARD_LOOKBEHIND, BAD_VALUE_RE, REFUSAL_RE, TEST_FAIL_CONTRACT_RE;
13441
13904
  var init_claims_defect = __esm({
13442
13905
  "../shared/dist/claims-defect.js"() {
13443
13906
  "use strict";
@@ -13481,7 +13944,10 @@ var init_claims_defect = __esm({
13481
13944
  NOT_A_CLEARING_RE = /\b(?:cancel|refresh|reload|preview|open|view|fail|hover|scroll|sort|filter|search)\w*/i;
13482
13945
  SUCCESS_NO_STATUS_RE = /\bsuccess\w*\b[^.;\n]{0,30}?\b(?:(?:do|does|did) not|don't|doesn't|didn't) (?:change|update|move)\b[^.;\n]{0,20}?\bstatus\b/i;
13483
13946
  OFF_BY_RE = /\boff[- ]by[- ](?:one|two|\d{1,3})\b/i;
13484
- NEGATED_SAFEGUARD_RE = /\bnot (?:rejected|validated|enforced|applied|persisted|saved)\b/i;
13947
+ NEGATED_SAFEGUARD_RE = /\bnot (rejected|validated|enforced|applied|persisted|saved)\b/gi;
13948
+ NOT_KEPT_RE = /^(?:applied|persisted|saved)$/i;
13949
+ REFUSED_INPUT_RE = /\b(?:blank|empty|whitespace|invalid|malformed|expired|duplicate[sd]?|too (?:long|short)|cancel(?:s|led|ed|ling|ing)?|discard\w*|escape)\b/i;
13950
+ SAFEGUARD_LOOKBEHIND = 80;
13485
13951
  BAD_VALUE_RE = /\b(?:incorrect|wrong|stale|negative|epoch|unix) (?:total|amount|value|date|count|price|label|plan|balance)\b/i;
13486
13952
  REFUSAL_RE = /\b(?<!not )(?:rejected|refused|denied|blocked|disallowed|prevented)\b|\breturns? (?:4\d\d|an? (?:error|validation))\b|\bshows (?:an? )?(?:error|validation)\b/i;
13487
13953
  TEST_FAIL_CONTRACT_RE = /\btest\.fail\s{0,20}\(/;
@@ -14340,7 +14806,7 @@ function findExpectCalls(code) {
14340
14806
  return out;
14341
14807
  }
14342
14808
  function countExpectCalls(code) {
14343
- return findExpectCalls(stripComments2(code)).length;
14809
+ return findExpectCalls(stripComments2(withExpectAliasesAsExpect(code))).length;
14344
14810
  }
14345
14811
  function countActionCalls(code) {
14346
14812
  return [...stripComments2(code).matchAll(ACTION_CALL)].length;
@@ -16669,6 +17135,7 @@ var init_test_authoring_quality = __esm({
16669
17135
  "../shared/dist/test-authoring-quality.js"() {
16670
17136
  "use strict";
16671
17137
  init_claims_defect();
17138
+ init_expect_call();
16672
17139
  init_readiness_poll();
16673
17140
  init_persistence_reread();
16674
17141
  init_write_response_check();
@@ -16795,7 +17262,7 @@ var init_test_authoring_quality = __esm({
16795
17262
  MAX_BALANCED_SCAN = 2048;
16796
17263
  MAX_CALLS = 500;
16797
17264
  MAX_SEEN_SCAN = 200;
16798
- ACTION_CALL = /\.(?:click|dblclick|fill|press|pressSequentially|type|check|uncheck|selectOption|setInputFiles|hover|tap|dragTo|goto)\s*\(/g;
17265
+ ACTION_CALL = /\.(?:(?:click|dblclick|fill|clear|press|pressSequentially|type|check|uncheck|setChecked|selectOption|setInputFiles|hover|tap|dragTo|dragAndDrop|dispatchEvent|goto)|(?:keyboard|mouse)\s*\.\s*(?:down|up|insertText|move|wheel))\s*\(/g;
16799
17266
  BY_NAME_LOCATOR = /\.getByRole\(\s{0,20}['"][^'"\n]{1,100}['"]\s{0,20},\s{0,20}\{[^}\n]{0,200}\bname\s{0,20}:|\.getByText\(|\.getByLabel\(|\.getByTitle\(|\.getByAltText\(|\.getByPlaceholder\(/;
16800
17267
  ACTION_AFTER = /^\s{0,20}\.\s{0,20}(?:click|fill|check|uncheck|press|type|hover|focus|selectOption|setInputFiles|dblclick|tap)\s{0,20}\(/;
16801
17268
  MAX_CONTACTS = 200;
@@ -18304,7 +18771,7 @@ function credentialEntries(maps) {
18304
18771
  }
18305
18772
  function replacePattern(value, form) {
18306
18773
  const escaped = form.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18307
- return new RegExp(value.length < WHOLE_WORD_BELOW ? `(?<![A-Za-z0-9_])${escaped}(?![A-Za-z0-9_])` : escaped, "gi");
18774
+ return value.length < WHOLE_WORD_BELOW ? `(?<![A-Za-z0-9_])${escaped}(?![A-Za-z0-9_])` : escaped;
18308
18775
  }
18309
18776
  function findCredentialTextLeftover(text, maps) {
18310
18777
  const bare = text.split(WITHHELD_CREDENTIAL_TEXT).join(" ").replace(REFERENCE, " ").toLowerCase();
@@ -18318,11 +18785,17 @@ function scrubCredentialText(text, maps) {
18318
18785
  const entries = credentialEntries(maps);
18319
18786
  if (entries.length === 0)
18320
18787
  return text;
18321
- let out = text;
18788
+ const alternatives = [];
18322
18789
  for (const [key, value] of entries) {
18323
18790
  for (const form of credentialValueForms(value))
18324
- out = out.replace(replacePattern(value, form), `{{secret:${key}}}`);
18791
+ alternatives.push({ pattern: replacePattern(value, form), key, length: form.length });
18325
18792
  }
18793
+ alternatives.sort((a, b) => b.length - a.length);
18794
+ const matcher = new RegExp([REFERENCE.source, ...alternatives.map((alt) => `(${alt.pattern})`)].join("|"), "gi");
18795
+ const out = text.replace(matcher, (...args) => {
18796
+ const index = args.slice(1, 1 + alternatives.length).findIndex((group) => group !== void 0);
18797
+ return index < 0 ? String(args[0]) : `{{secret:${alternatives[index].key}}}`;
18798
+ });
18326
18799
  return findCredentialTextLeftover(out, maps) ? WITHHELD_CREDENTIAL_TEXT : out;
18327
18800
  }
18328
18801
  function scrubCredentialStringsIn(value, maps) {
@@ -18816,10 +19289,24 @@ function onlyUrlAfter(code, writeAt) {
18816
19289
  i = at + 6;
18817
19290
  }
18818
19291
  }
19292
+ function readsOrderAfter(code, writeAt) {
19293
+ const rest = code.slice(writeAt + 1);
19294
+ if (ORDER_READ_MARKERS.some((m) => rest.includes(m)) && nextExpect(code, writeAt + 1) >= 0)
19295
+ return true;
19296
+ for (let i = writeAt + 1; ; ) {
19297
+ const at = nextExpect(code, i);
19298
+ if (at < 0)
19299
+ return false;
19300
+ if (ORDERED_ARRAY_EXPECT.test(expectStatement(code, at)))
19301
+ return true;
19302
+ i = at + 6;
19303
+ }
19304
+ }
18819
19305
  function assessTitlePromiseShape(claimText, code) {
18820
19306
  const persistence = PERSISTENCE_RE.test(claimText);
18821
19307
  const refusal = REFUSAL_RE2.test(claimText);
18822
- if (!persistence && !refusal)
19308
+ const order = ORDER_RE.test(claimText) || ORDER_RE_CASED.test(claimText);
19309
+ if (!persistence && !refusal && !order)
18823
19310
  return [];
18824
19311
  const blank = blankCommentsAndStringLiterals(code);
18825
19312
  const writeAt = proofAnchor(blank);
@@ -18832,9 +19319,12 @@ function assessTitlePromiseShape(claimText, code) {
18832
19319
  if (refusal && onlyUrlAfter(blank, writeAt)) {
18833
19320
  findings.push({ rule: "rejection-only-url", reason: REFUSAL_REASON });
18834
19321
  }
19322
+ if (order && !readsOrderAfter(blank, writeAt)) {
19323
+ findings.push({ rule: "sort-without-order", reason: ORDER_REASON });
19324
+ }
18835
19325
  return findings;
18836
19326
  }
18837
- var PERSIST_REASON, REFUSAL_REASON, PERSISTENCE_RE, REFUSAL_RE2, WRITE_MARKERS, VALUE_WRITE_MARKERS, REREAD_MARKERS;
19327
+ var PERSIST_REASON, REFUSAL_REASON, PERSISTENCE_RE, REFUSAL_RE2, ORDER_REASON, ORDER_RE, ORDER_RE_CASED, ORDER_READ_MARKERS, ORDERED_ARRAY_EXPECT, WRITE_MARKERS, VALUE_WRITE_MARKERS, REREAD_MARKERS;
18838
19328
  var init_title_promise_shape = __esm({
18839
19329
  "../shared/dist/title-promise-shape.js"() {
18840
19330
  "use strict";
@@ -18843,6 +19333,21 @@ var init_title_promise_shape = __esm({
18843
19333
  REFUSAL_REASON = "The title says the app refuses the input, but the only check after submitting is the URL: assert the error message, the field's invalid state or the response status.";
18844
19334
  PERSISTENCE_RE = /\b(?:is|are|gets|stays)\s{1,20}saved\b|\b(?:is|are)\s{1,20}persisted\b|\bpersists\s{1,20}(?:after|across|through)\s{1,20}(?:a\s{1,20})?(?:reloads?|refresh(?:es)?|sessions?)\b|\bis\s{1,20}(?:stored|kept|remembered)\b|\bafter\s{1,20}(?:a\s{1,20})?reload\b|\bafter\s{1,20}refresh\b|\bsurvives\s{1,20}(?:a\s{1,20})?reload\b/i;
18845
19335
  REFUSAL_RE2 = /\b(?:rejects|rejected|refuses|refused|prevents|prevented|disallows)\b|\b(?:is|are|gets)\s{1,20}blocked\b|\bis\s{1,20}denied\b|\bshows\s{1,20}an\s{1,20}error\b|\bshows\s{1,20}a\s{1,20}validation\b|\bshows\s{1,20}validation\b|\breturns\s{1,20}4\d\d\b/i;
19336
+ ORDER_REASON = "The title says the list is sorted, but no check after the sort reads the order: assert the items in order (toHaveText([...]) on the list, the first and last item, or allTextContents() compared with a sorted copy).";
19337
+ ORDER_RE = /\bsort(?:s|ed|ing)?\b[^\n.]{0,60}?\bby\b|\b(?:ascending|descending)\b|\b(?:low|lowest)\s{1,20}to\s{1,20}(?:high|highest)\b|\b(?:high|highest)\s{1,20}to\s{1,20}(?:low|lowest)\b|\balphabetical(?:ly)?\b|\b(?:newest|oldest)\s{1,20}first\b/i;
19338
+ ORDER_RE_CASED = /\b[AZ]\s{0,3}(?:to|-|→)\s{0,3}[AZ]\b/;
19339
+ ORDER_READ_MARKERS = [
19340
+ ".allTextContents(",
19341
+ ".allInnerTexts(",
19342
+ ".evaluateAll(",
19343
+ ".toMatchAriaSnapshot(",
19344
+ ".sort(",
19345
+ ".toSorted(",
19346
+ ".first()",
19347
+ ".last()",
19348
+ ".nth("
19349
+ ];
19350
+ ORDERED_ARRAY_EXPECT = /\.(?:toHaveText|toContainText|toEqual|toStrictEqual)\s*\(\s*\[/;
18846
19351
  WRITE_MARKERS = [
18847
19352
  ".fill(",
18848
19353
  ".click(",
@@ -19174,7 +19679,7 @@ function assertionsReadVisibleContent(code) {
19174
19679
  });
19175
19680
  }
19176
19681
  function isLegacyReviewFlag(tags, activity) {
19177
- const flagged = tags.includes(NEEDS_REVIEW_TAG) || activity?.needsReview === true;
19682
+ const flagged = tags.includes(NEEDS_REVIEW_TAG2) || activity?.needsReview === true;
19178
19683
  const source = activity?.needsReviewSource;
19179
19684
  return flagged && !(typeof source === "string" && source in SOURCE_RANK);
19180
19685
  }
@@ -19236,7 +19741,7 @@ function recheckGeneratorNotes(notes, input) {
19236
19741
  }
19237
19742
  function reviewHolds(tags, activity, cleared = /* @__PURE__ */ new Set()) {
19238
19743
  const out = [];
19239
- const tagged = tags.includes(NEEDS_REVIEW_TAG);
19744
+ const tagged = tags.includes(NEEDS_REVIEW_TAG2);
19240
19745
  const generatorAsked = activity?.needsReview === true;
19241
19746
  if (tagged) {
19242
19747
  if (activity?.needsReviewSource === "human")
@@ -19296,7 +19801,7 @@ function evaluatePromotionGate(input, checks = PROMOTION_QUALITY_CHECKS) {
19296
19801
  ...suspiciousTagged ? { suspiciousPass: { mode: suspicious, covered: suspiciousCovered, wouldHold: suspiciousHold } } : {}
19297
19802
  };
19298
19803
  }
19299
- var SOURCE_RANK, strengthCheck, STRING_CONST, TYPED_VALUE, TYPED_CONSTANT, MAX_STATEMENTS, MAX_STATEMENT_CHARS, LITERAL_OR_IDENTIFIER, MUTATING_ACTION, FEEDBACK_REGION, ONE_SHOT_MESSAGE_REASON, FEEDBACK_REGION_ALL, dynamicAssertionCheck, OUTCOME_KIND_MATCHER, ROUTE_TERM, ROUTE_TRAILING_PUNCTUATION, QUOTED_LITERAL, GENERIC_LITERAL, MAX_OUTCOME_STATEMENTS, MAX_OUTCOMES, MAX_OUTCOME_TEXT_CHARS, expectedOutcomeCheck, testDataCheck, sharedAccountCheck, clockLiteralCheck, readinessPollCheck, knownBugTestCheck, bugClaimContractCheck, suspiciousExpectationCheck, bugCandidateCheck, conventionOutcomesCheck, pendingHook, locatorFragmentCheck, FILTER_STEP, NEXT_ACTION, REDUCTION_PROOF, MAX_FILTER_LINES, MAX_FILTER_LINE_CHARS, filterPositiveOnlyCheck, PROMOTION_QUALITY_CHECKS, PROMOTION_GATE_MAX_CODE_CHARS, NOT_EVALUATED, SUSPICIOUS_PASS_TAG, NOT_CONTENT_MATCHER, CONTENT_PROOF, NAMED_ELEMENT, NEEDS_REVIEW_TAG, count2, RECHECKED_NOTES, EVIDENCE_NOTE_PREFIXES, PENDING_RECHECK_NOTE_PREFIXES, STRENGTH_RANK;
19804
+ 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_TAG2, count2, RECHECKED_NOTES, EVIDENCE_NOTE_PREFIXES, PENDING_RECHECK_NOTE_PREFIXES, STRENGTH_RANK;
19300
19805
  var init_promotion_quality_gate = __esm({
19301
19806
  "../shared/dist/promotion-quality-gate.js"() {
19302
19807
  "use strict";
@@ -19315,8 +19820,8 @@ var init_promotion_quality_gate = __esm({
19315
19820
  SOURCE_RANK = { generator: 0, classifier: 0, healer: 1, human: 2 };
19316
19821
  strengthCheck = {
19317
19822
  id: "assertion-strength",
19318
- run({ code, tags, strengthGate, flagContradictedAdditions, lastActionProof }) {
19319
- const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true, lastActionProof });
19823
+ run({ code, tags, strengthGate, flagContradictedAdditions, lastActionProof, absenceProof }) {
19824
+ const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true, lastActionProof, absenceProof });
19320
19825
  if (verdict.severity === "route-bug")
19321
19826
  return [{ check: "route-bug" }];
19322
19827
  const authDowngrade = verdict.severity === "downgrade" && tags.some((t) => t.toLowerCase() === "@auth");
@@ -19574,7 +20079,7 @@ ${goalText}`);
19574
20079
  NOT_CONTENT_MATCHER = /\.(?:toHaveURL|toHaveTitle|toBeOK)\s*\(|\bwaitForURL\s*\(|\.(?:status|ok)\s*\(\s*\)/;
19575
20080
  CONTENT_PROOF = /* @__PURE__ */ new Set(["controlled", "revealed", "value"]);
19576
20081
  NAMED_ELEMENT = /\bgetByRole\s*\(\s*['"`](?!(?:navigation|banner|main|contentinfo|complementary|region|search)['"`])\w{1,30}['"`]\s*,\s*\{[^}]{0,200}?\bname\s*:\s*(['"`])[^'"`$\n]{1,200}\1|\bgetByLabel\s*\(\s*(['"`])[^'"`$\n]{1,200}\2/;
19577
- NEEDS_REVIEW_TAG = "@needs-review";
20082
+ NEEDS_REVIEW_TAG2 = "@needs-review";
19578
20083
  count2 = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
19579
20084
  RECHECKED_NOTES = [
19580
20085
  {
@@ -19695,11 +20200,18 @@ var init_regex_alternation = __esm({
19695
20200
  function isPageEvidenceEntry(entry) {
19696
20201
  return typeof entry === "object" && entry !== null && entry.outcomeType === PAGE_LOADING_OUTCOME_TYPE;
19697
20202
  }
19698
- var PAGE_LOADING_OUTCOME_TYPE;
20203
+ function pageScreenshotKey(projectId, safeName, stillLoading = false) {
20204
+ return `discovery/${projectId}/screenshots/${stillLoading ? `${STILL_LOADING_SCREENSHOT_DIR}/` : ""}${safeName}.png`;
20205
+ }
20206
+ function isStillLoadingScreenshotKey(key) {
20207
+ return typeof key === "string" && key.includes(`/screenshots/${STILL_LOADING_SCREENSHOT_DIR}/`);
20208
+ }
20209
+ var PAGE_LOADING_OUTCOME_TYPE, STILL_LOADING_SCREENSHOT_DIR;
19699
20210
  var init_page_evidence = __esm({
19700
20211
  "../shared/dist/page-evidence.js"() {
19701
20212
  "use strict";
19702
20213
  PAGE_LOADING_OUTCOME_TYPE = "LOADING";
20214
+ STILL_LOADING_SCREENSHOT_DIR = "still-loading";
19703
20215
  }
19704
20216
  });
19705
20217
 
@@ -20104,11 +20616,13 @@ __export(dist_exports, {
20104
20616
  SERVER_INTERRUPT_ERROR_PREFIX: () => SERVER_INTERRUPT_ERROR_PREFIX,
20105
20617
  SHARED_PRICING_PLAN_MAP: () => SHARED_PRICING_PLAN_MAP,
20106
20618
  SHARED_SERVER_APPLICATION_NAME: () => SHARED_SERVER_APPLICATION_NAME,
20619
+ SMS_NOT_RECEIVED_PREFIXES: () => SMS_NOT_RECEIVED_PREFIXES,
20107
20620
  SPEND_SOURCE_KEY: () => SPEND_SOURCE_KEY,
20108
20621
  SPEND_SOURCE_SERVER: () => SPEND_SOURCE_SERVER,
20109
20622
  STATED_LIMITS_VERSION: () => STATED_LIMITS_VERSION,
20110
20623
  STATED_PATTERN_MAX: () => STATED_PATTERN_MAX,
20111
20624
  STATIC_STANDARD_MIN_INTERACTIVE: () => STATIC_STANDARD_MIN_INTERACTIVE,
20625
+ STILL_LOADING_SCREENSHOT_DIR: () => STILL_LOADING_SCREENSHOT_DIR,
20112
20626
  STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
20113
20627
  SUCCESS_NOTICE_TEXT: () => SUCCESS_NOTICE_TEXT,
20114
20628
  SUPPORT_EMAIL: () => SUPPORT_EMAIL,
@@ -20136,6 +20650,7 @@ __export(dist_exports, {
20136
20650
  TTS_VOICE_OPTIONS: () => TTS_VOICE_OPTIONS,
20137
20651
  UI_VARIANTS: () => UI_VARIANTS,
20138
20652
  UNEXPECTED_PASS_ERROR_PREFIX: () => UNEXPECTED_PASS_ERROR_PREFIX,
20653
+ UNVERIFIED_HEAL_CONFIDENCES: () => UNVERIFIED_HEAL_CONFIDENCES,
20139
20654
  USER_CANCELLED_RUN_ERROR: () => USER_CANCELLED_RUN_ERROR,
20140
20655
  VISUAL_MASK_SELECTORS_MAX: () => VISUAL_MASK_SELECTORS_MAX,
20141
20656
  VISUAL_MASK_SELECTOR_FORBIDDEN: () => VISUAL_MASK_SELECTOR_FORBIDDEN,
@@ -20189,6 +20704,7 @@ __export(dist_exports, {
20189
20704
  blankBlockComments: () => blankBlockComments3,
20190
20705
  blankCommentsAndStringLiterals: () => blankCommentsAndStringLiterals,
20191
20706
  blankKeepingLines: () => blankKeepingLines,
20707
+ blockedRunLogLine: () => blockedRunLogLine,
20192
20708
  boundedCallCostUsd: () => boundedCallCostUsd,
20193
20709
  brokerResponseBody: () => brokerResponseBody,
20194
20710
  bugFoundQuarantineWhere: () => bugFoundQuarantineWhere,
@@ -20294,6 +20810,7 @@ __export(dist_exports, {
20294
20810
  evaluatePromotionGate: () => evaluatePromotionGate,
20295
20811
  excludeCancelledRunsAnd: () => excludeCancelledRunsAnd,
20296
20812
  excludeCancelledRunsWhere: () => excludeCancelledRunsWhere,
20813
+ expectAliasNames: () => expectAliasNames,
20297
20814
  explicitAgenticCliOverrideError: () => explicitAgenticCliOverrideError,
20298
20815
  extractAssertedControlSignatures: () => extractAssertedControlSignatures,
20299
20816
  extractMuseAssistantJson: () => extractMuseAssistantJson,
@@ -20360,6 +20877,8 @@ __export(dist_exports, {
20360
20877
  googleGisRequestUrl: () => googleGisRequestUrl,
20361
20878
  googleSignInLimitLines: () => googleSignInLimitLines,
20362
20879
  googleSignInPolicyProblems: () => googleSignInPolicyProblems,
20880
+ googleSignInRefusedLine: () => googleSignInRefusedLine,
20881
+ googleSignInRefusedReason: () => googleSignInRefusedReason,
20363
20882
  hasCrossPlatformIdentity: () => hasCrossPlatformIdentity,
20364
20883
  hasEncodedRequestBody: () => hasEncodedRequestBody,
20365
20884
  hasEngineSpecParts: () => hasEngineSpecParts,
@@ -20403,6 +20922,7 @@ __export(dist_exports, {
20403
20922
  isCodeFactNote: () => isCodeFactNote,
20404
20923
  isConfigLikeReason: () => isConfigLikeReason,
20405
20924
  isCountedFailure: () => isCountedFailure,
20925
+ isCredentialHeaderName: () => isCredentialHeaderName,
20406
20926
  isCreditPackAmountCents: () => isCreditPackAmountCents,
20407
20927
  isDbPricingPlan: () => isDbPricingPlan,
20408
20928
  isDeniedGoogleBrokerRedirectHost: () => isDeniedGoogleBrokerRedirectHost,
@@ -20458,8 +20978,10 @@ __export(dist_exports, {
20458
20978
  isSharedAuthTenantHost: () => isSharedAuthTenantHost,
20459
20979
  isSharedSuffix: () => isSharedSuffix,
20460
20980
  isSingleEmailAddress: () => isSingleEmailAddress,
20981
+ isSmsNotReceivedError: () => isSmsNotReceivedError,
20461
20982
  isSoftOutcome: () => isSoftOutcome,
20462
20983
  isStateValueMobileClassName: () => isStateValueMobileClassName,
20984
+ isStillLoadingScreenshotKey: () => isStillLoadingScreenshotKey,
20463
20985
  isSttModelForProvider: () => isSttModelForProvider,
20464
20986
  isTestReadyForPlatform: () => isTestReadyForPlatform,
20465
20987
  isTestVariableSecretKey: () => isTestVariableSecretKey,
@@ -20467,6 +20989,7 @@ __export(dist_exports, {
20467
20989
  isUIVariant: () => isUIVariant,
20468
20990
  isUnexpectedPassError: () => isUnexpectedPassError,
20469
20991
  isUnsafePrecondition: () => isUnsafePrecondition,
20992
+ isUnverifiedHealPass: () => isUnverifiedHealPass,
20470
20993
  isUsableDeepgramKey: () => isUsableDeepgramKey,
20471
20994
  isUserCancelledError: () => isUserCancelledError,
20472
20995
  isValidAgenticCliModel: () => isValidAgenticCliModel,
@@ -20491,8 +21014,10 @@ __export(dist_exports, {
20491
21014
  lintTestData: () => lintTestData,
20492
21015
  lintTestQuality: () => lintTestQuality,
20493
21016
  literalUrlRegex: () => literalUrlRegex,
21017
+ loginFormAlertTexts: () => loginFormAlertTexts,
20494
21018
  longCredentialValues: () => longCredentialValues,
20495
21019
  maskedOutsideStrings: () => maskedOutsideStrings,
21020
+ mayBeUnverifiedHealPass: () => mayBeUnverifiedHealPass,
20496
21021
  mediumForMobilePlatform: () => mediumForMobilePlatform,
20497
21022
  mergePageType: () => mergePageType,
20498
21023
  mergeSuspectedBugsAcrossSurveys: () => mergeSuspectedBugsAcrossSurveys,
@@ -20526,7 +21051,9 @@ __export(dist_exports, {
20526
21051
  opensRegexLiteral: () => opensRegexLiteral,
20527
21052
  otherMobilePlatform: () => otherMobilePlatform,
20528
21053
  outcomeNamedByAssertion: () => outcomeNamedByAssertion,
21054
+ pageScreenshotKey: () => pageScreenshotKey,
20529
21055
  parenDepthDelta: () => parenDepthDelta,
21056
+ parseAbsenceProofMode: () => parseAbsenceProofMode,
20530
21057
  parseApiCardModelUpdate: () => parseApiCardModelUpdate,
20531
21058
  parseAppTimezone: () => parseAppTimezone,
20532
21059
  parseAuditGroupScopeId: () => parseAuditGroupScopeId,
@@ -20602,7 +21129,7 @@ __export(dist_exports, {
20602
21129
  redactNeedlesText: () => redactNeedlesText,
20603
21130
  redirectOnAppSite: () => redirectOnAppSite,
20604
21131
  refuseEncodedRequestBodies: () => refuseEncodedRequestBodies,
20605
- regexLiteralEnd: () => regexLiteralEnd2,
21132
+ regexLiteralEnd: () => regexLiteralEnd,
20606
21133
  regexOpensAfter: () => regexOpensAfter,
20607
21134
  registrableDomain: () => registrableDomain,
20608
21135
  renderIdentity: () => renderIdentity,
@@ -20644,6 +21171,9 @@ __export(dist_exports, {
20644
21171
  saveOpensConfirm: () => saveOpensConfirm,
20645
21172
  scanClockDependentLiterals: () => scanClockDependentLiterals,
20646
21173
  scanHtmlTags: () => scanHtmlTags,
21174
+ scrubCredentialHeaderValue: () => scrubCredentialHeaderValue,
21175
+ scrubCredentialHeaders: () => scrubCredentialHeaders,
21176
+ scrubCredentialHeadersDeep: () => scrubCredentialHeadersDeep,
20647
21177
  scrubCredentialStringsIn: () => scrubCredentialStringsIn,
20648
21178
  scrubCredentialText: () => scrubCredentialText,
20649
21179
  scrubSearchParams: () => scrubSearchParams,
@@ -20654,9 +21184,12 @@ __export(dist_exports, {
20654
21184
  selectorForValue: () => selectorForValue,
20655
21185
  setAgenticCliRuntime: () => setAgenticCliRuntime,
20656
21186
  setProviderChainOverrides: () => setProviderChainOverrides,
21187
+ signInStepPassedBeforeFailure: () => signInStepPassedBeforeFailure,
21188
+ signsInInline: () => signsInInline,
20657
21189
  signupPlanFor: () => signupPlanFor,
20658
21190
  simplifyMobileWanderingInPlace: () => simplifyMobileWanderingInPlace,
20659
21191
  siteOf: () => siteOf,
21192
+ specCallsExpect: () => specCallsExpect,
20660
21193
  specItemsFromTestPlans: () => specItemsFromTestPlans,
20661
21194
  splitAnswerLines: () => splitAnswerLines,
20662
21195
  statedLimitsFromAttrs: () => statedLimitsFromAttrs,
@@ -20704,6 +21237,7 @@ __export(dist_exports, {
20704
21237
  visualProDailyBudget: () => visualProDailyBudget,
20705
21238
  voiceMatchesTtsModel: () => voiceMatchesTtsModel,
20706
21239
  voicesForTtsModel: () => voicesForTtsModel,
21240
+ withExpectAliasesAsExpect: () => withExpectAliasesAsExpect,
20707
21241
  withGeneratorReviewSource: () => withGeneratorReviewSource,
20708
21242
  withReviewSource: () => withReviewSource,
20709
21243
  withoutNotChargedClaim: () => withoutNotChargedClaim,
@@ -20758,6 +21292,7 @@ var init_dist = __esm({
20758
21292
  init_encoded_forms();
20759
21293
  init_block_comments();
20760
21294
  init_redact_needles();
21295
+ init_credential_header_scrub();
20761
21296
  init_voice_config();
20762
21297
  init_deepgram_speech();
20763
21298
  init_deepgram_voices();
@@ -20941,6 +21476,7 @@ var require_repo_brief = __commonJS({
20941
21476
  exports2.briefSectionForFeature = briefSectionForFeature;
20942
21477
  exports2.buildBriefRouteAllowlist = buildBriefRouteAllowlist;
20943
21478
  exports2.buildBriefDoNotTestRoutes = buildBriefDoNotTestRoutes;
21479
+ exports2.briefJobRoute = briefJobRoute;
20944
21480
  exports2.unopenedBriefJobs = unopenedBriefJobs;
20945
21481
  var shared_1 = (init_dist(), __toCommonJS(dist_exports));
20946
21482
  var model_json_document_js_1 = require_model_json_document();
@@ -21199,14 +21735,25 @@ ${scenarios}` : ""
21199
21735
  }
21200
21736
  return [...routes];
21201
21737
  }
21202
- function unopenedBriefJobs(brief, coveredText) {
21738
+ var ENTRY_ROUTE_RE = /(?:^|[\s(])((?:\/|#\/)[^\s?,;()]*)/;
21739
+ function briefJobRoute(job) {
21740
+ const match = ENTRY_ROUTE_RE.exec(job.entry.trim());
21741
+ if (!match)
21742
+ return null;
21743
+ const route = match[1].replace(/[.:]+$/, "");
21744
+ return route === "#/" ? null : route || null;
21745
+ }
21746
+ function unopenedBriefJobs(brief, coveredText, isOpenedRoute) {
21203
21747
  const haystack = coveredText.toLowerCase();
21204
21748
  return brief.jobs.filter((job) => {
21205
21749
  if (job.doNotTest)
21206
21750
  return false;
21207
21751
  const name = job.name.toLowerCase();
21208
21752
  const entry = job.entry.toLowerCase();
21209
- return !haystack.includes(name) && !haystack.includes(entry);
21753
+ if (haystack.includes(name) || haystack.includes(entry))
21754
+ return false;
21755
+ const route = isOpenedRoute ? briefJobRoute(job) : null;
21756
+ return !(route && isOpenedRoute?.(route));
21210
21757
  });
21211
21758
  }
21212
21759
  exports2.REPO_BRIEF_PROMPT = [
@@ -22254,6 +22801,7 @@ var require_survey_network_guard = __commonJS({
22254
22801
  exports2.graphqlUrlCarriesQuery = graphqlUrlCarriesQuery;
22255
22802
  exports2.graphqlUrlQueriesAreReads = graphqlUrlQueriesAreReads;
22256
22803
  exports2.graphqlGetMayWrite = graphqlGetMayWrite;
22804
+ exports2.queryMethodMayWrite = queryMethodMayWrite;
22257
22805
  exports2.writeShapedGet = writeShapedGet;
22258
22806
  exports2.decideSurveyRequest = decideSurveyRequest;
22259
22807
  exports2.installSurveyNetworkGuard = installSurveyNetworkGuard;
@@ -22818,6 +23366,12 @@ var require_survey_network_guard = __commonJS({
22818
23366
  const docs = url.searchParams.getAll("query");
22819
23367
  return docs.length > 0 && (typeof body === "string" && body.length > 0 || docs.some((doc) => !graphqlDocumentIsQueryOnly(doc)));
22820
23368
  }
23369
+ function queryMethodMayWrite(url, body, graphqlEndpoint) {
23370
+ if (graphqlEndpoint)
23371
+ return true;
23372
+ const operation = /\b(?:mutation|subscription)\b/i;
23373
+ return typeof body === "string" && operation.test(body) || [...url.searchParams.values()].some((v) => operation.test(v));
23374
+ }
22821
23375
  function isGraphqlGetWrite(url, s, body) {
22822
23376
  return isGraphqlEndpoint(url, s) && graphqlGetMayWrite(url, body);
22823
23377
  }
@@ -22845,6 +23399,7 @@ var require_survey_network_guard = __commonJS({
22845
23399
  "log-out"
22846
23400
  ]);
22847
23401
  exports2.AMBIGUOUS_WRITE_VERBS = /* @__PURE__ */ new Set(["archive", "confirm", "disable"]);
23402
+ var LABEL_PAGE_NOUNS = /* @__PURE__ */ new Set(["archive"]);
22848
23403
  var RESET_PAGE_WORDS = /* @__PURE__ */ new Set(["reset-password", "password-reset", "reset_password", "password_reset", "resetpassword", "forgot-password"]);
22849
23404
  var isTokenLikeSegment = (seg) => exports2.ID_SEGMENT_RE.test(seg) || /^[A-Za-z0-9_-]{20,}$/.test(seg);
22850
23405
  var WRITE_SHAPED_STATIC_EXTENSIONS = [
@@ -22990,6 +23545,8 @@ var require_survey_network_guard = __commonJS({
22990
23545
  if (oneTimeToken)
22991
23546
  return { reason: "one-time-token" };
22992
23547
  const verb = opts.triggerLabel ? labelVerb(opts.triggerLabel) : void 0;
23548
+ if (verb && LABEL_PAGE_NOUNS.has(verb) && last === verb && params.length === 0)
23549
+ return null;
22993
23550
  return verb && !opts.ignoreVerbs?.has(verb) ? { reason: "label", verb } : null;
22994
23551
  }
22995
23552
  function writeShapedText(w) {
@@ -23024,9 +23581,10 @@ var require_survey_network_guard = __commonJS({
23024
23581
  return { action: "allow", rule: readMethod ? "read" : "non-http" };
23025
23582
  const appSite = isAppSite(url.hostname, s.appBaseUrl);
23026
23583
  const graphqlGet = method === "GET" && isGraphqlGetWrite(url, s, req.postData);
23584
+ const queryRead = method === "QUERY" && !queryMethodMayWrite(url, req.postData, isGraphqlEndpoint(url, s));
23027
23585
  const inActionWindow = s.lastActionAt > 0 && s.now - s.lastActionAt <= ACTION_NAVIGATION_WINDOW_MS;
23028
23586
  const typedLoad = s.agentLoad === true && req.resourceType === "document";
23029
- const writeShaped = method === "GET" && appSite && !graphqlGet && (inActionWindow || typedLoad) ? writeShapedGet(req.url, {
23587
+ const writeShaped = (method === "GET" || queryRead) && appSite && !graphqlGet && (inActionWindow || typedLoad) ? writeShapedGet(req.url, {
23030
23588
  appSites: appSitesOf(s.appBaseUrl),
23031
23589
  resourceType: req.resourceType ?? "",
23032
23590
  // A label says what a click opens: it only judges a page load, never the app's own fetches, and never a typed URL.
@@ -23034,7 +23592,7 @@ var require_survey_network_guard = __commonJS({
23034
23592
  }) : null;
23035
23593
  if (writeShaped)
23036
23594
  return decideWriteShapedGet(req, s, writeShaped);
23037
- if (readMethod && !graphqlGet)
23595
+ if ((readMethod || queryRead) && !graphqlGet)
23038
23596
  return { action: "allow", rule: "read" };
23039
23597
  if (!appSite && ((0, shared_1.isAuthProviderHost)(url.hostname) || isTelemetryHost(url.hostname)))
23040
23598
  return { action: "allow", rule: "third-party" };
@@ -24690,8 +25248,9 @@ var require_repo_feature_verification = __commonJS({
24690
25248
  "the",
24691
25249
  "for"
24692
25250
  ]);
25251
+ var UNSPACED_SCRIPT = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
24693
25252
  function rootCardTerms(featureName) {
24694
- const words = String(featureName ?? "").toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 4 && !ROOT_GENERIC_WORDS.has(w));
25253
+ const words = String(featureName ?? "").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((w) => [...w].length >= (UNSPACED_SCRIPT.test(w) ? 2 : 4) && !ROOT_GENERIC_WORDS.has(w));
24695
25254
  return [...new Set(words.map((w) => w.length > 4 && w.endsWith("s") ? w.slice(0, -1) : w))];
24696
25255
  }
24697
25256
  function textShowsAnyTerm(text, terms) {
@@ -24745,7 +25304,7 @@ var require_repo_feature_verification = __commonJS({
24745
25304
  return null;
24746
25305
  }
24747
25306
  function normalizeControlText(text) {
24748
- return text.normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}\p{M}]+/gu, " ").trim();
25307
+ return text.normalize("NFKD").replace(/(\p{Script=Latin})\p{M}+/gu, "$1").normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}\p{M}]+/gu, " ").trim();
24749
25308
  }
24750
25309
  var SESSION_END_LABEL = /^(?:log|sign) ?(?:out|off)$/;
24751
25310
  var SESSION_END_SPELLINGS = ["log out", "logout", "log off", "logoff", "sign out", "signout", "sign off", "signoff"];
@@ -24758,13 +25317,35 @@ var require_repo_feature_verification = __commonJS({
24758
25317
  return [];
24759
25318
  return SESSION_END_LABEL.test(wanted) ? SESSION_END_SPELLINGS : [wanted];
24760
25319
  }
25320
+ var SHORT_LABEL_CHARS = 2;
25321
+ var WORD_SEGMENTER = new Intl.Segmenter(void 0, { granularity: "word" });
25322
+ function wholeWordsSpell(text, wanted) {
25323
+ const words = [];
25324
+ for (const s of WORD_SEGMENTER.segment(text))
25325
+ if (s.isWordLike)
25326
+ words.push(s.segment);
25327
+ for (let i = 0; i < words.length; i++) {
25328
+ let run = "";
25329
+ for (let j = i; j < words.length && run.length < wanted.length; j++) {
25330
+ run += words[j];
25331
+ if (run === wanted)
25332
+ return true;
25333
+ }
25334
+ }
25335
+ return false;
25336
+ }
24761
25337
  function textNamesControl(text, label) {
24762
25338
  const spellings = controlSpellings(label);
24763
25339
  if (spellings.length === 0)
24764
25340
  return false;
24765
25341
  const haystack = normalizeControlText(text);
24766
25342
  const squeezed = haystack.replace(/ /g, "");
24767
- return spellings.some((w) => haystack.includes(w) || squeezed.includes(w.replace(/ /g, "")));
25343
+ return spellings.some((w) => {
25344
+ const bare = w.replace(/ /g, "");
25345
+ if (!haystack.includes(w) && !squeezed.includes(bare))
25346
+ return false;
25347
+ return [...bare].length > SHORT_LABEL_CHARS || wholeWordsSpell(haystack, bare);
25348
+ });
24768
25349
  }
24769
25350
  function pageShowsControl(page, label) {
24770
25351
  const text = capturedPageText(page);