@validate.qa/runner 1.0.100 → 1.0.101

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.
@@ -3027,7 +3027,7 @@ function unescapeQuotedBody(raw) {
3027
3027
  return raw.replace(/\\(['"`\\])/g, "$1");
3028
3028
  }
3029
3029
  function extractRoleNameValue(line) {
3030
- const m = line.match(new RegExp(`getByRole\\s*\\([^,]+,\\s*\\{[^}]*name\\s*:\\s*(?:${QUOTED_BODY}|\\/([^/]*?)\\/)`, "i"));
3030
+ const m = line.match(new RegExp(`getByRole\\s*\\([^,]{1,200},\\s*\\{[^}]{0,200}?name\\s*:\\s*(?:${QUOTED_BODY}|\\/([^/]*?)\\/)`, "i"));
3031
3031
  if (!m)
3032
3032
  return null;
3033
3033
  const raw = m[1] ?? m[2] ?? m[3];
@@ -3735,6 +3735,8 @@ function stripCoverageComments(code) {
3735
3735
  let copyFrom = 0;
3736
3736
  let i = 0;
3737
3737
  let quote = null;
3738
+ let prev = "";
3739
+ let noRegexBefore = 0;
3738
3740
  while (i < code.length) {
3739
3741
  const ch = code[i];
3740
3742
  if (quote) {
@@ -3742,12 +3744,24 @@ function stripCoverageComments(code) {
3742
3744
  i += 2;
3743
3745
  continue;
3744
3746
  }
3745
- if (ch === quote || ch === "\n" && quote !== "`")
3747
+ if (ch === quote || ch === "\n" && quote !== "`") {
3746
3748
  quote = null;
3749
+ prev = ch === "\n" ? prev : ch;
3750
+ }
3747
3751
  i++;
3748
3752
  continue;
3749
3753
  }
3750
3754
  const next = code[i + 1];
3755
+ if (ch === "/" && next !== "*" && next !== "/" && i >= noRegexBefore && (prev === "" || REGEX_PRECEDER2.includes(prev))) {
3756
+ const end = regexLiteralEnd3(code, i);
3757
+ if (end > 0) {
3758
+ i = end;
3759
+ prev = "a";
3760
+ continue;
3761
+ }
3762
+ const eol = code.indexOf("\n", i);
3763
+ noRegexBefore = eol === -1 ? code.length : eol;
3764
+ }
3751
3765
  if (ch === "/" && next === "*") {
3752
3766
  const end = code.indexOf("*/", i + 2);
3753
3767
  const stop = end === -1 ? code.length : end + 2;
@@ -3766,11 +3780,39 @@ function stripCoverageComments(code) {
3766
3780
  }
3767
3781
  if (ch === '"' || ch === "'" || ch === "`")
3768
3782
  quote = ch;
3783
+ if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r")
3784
+ prev = ch;
3769
3785
  i++;
3770
3786
  }
3771
3787
  out.push(code.slice(copyFrom));
3772
3788
  return out.join("");
3773
3789
  }
3790
+ function regexLiteralEnd3(code, start) {
3791
+ let inClass = false;
3792
+ for (let j = start + 1; j < code.length; j++) {
3793
+ const c = code[j];
3794
+ if (c === "\n")
3795
+ return -1;
3796
+ if (c === "\\") {
3797
+ j++;
3798
+ continue;
3799
+ }
3800
+ if (inClass) {
3801
+ if (c === "]")
3802
+ inClass = false;
3803
+ continue;
3804
+ }
3805
+ if (c === "[")
3806
+ inClass = true;
3807
+ else if (c === "/") {
3808
+ let k = j + 1;
3809
+ while (k < code.length && isIdentChar(code[k]))
3810
+ k++;
3811
+ return k;
3812
+ }
3813
+ }
3814
+ return -1;
3815
+ }
3774
3816
  function isIdentChar(ch) {
3775
3817
  if (!ch)
3776
3818
  return false;
@@ -4428,7 +4470,7 @@ function assessTitleCoverage(title, code) {
4428
4470
  const repeatedHit = [...repeated].some((term) => hay.includes(term));
4429
4471
  return { covered: hit >= needed || repeatedHit, missingTerms };
4430
4472
  }
4431
- var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
4473
+ var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, REGEX_PRECEDER2, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
4432
4474
  var init_expected_outcome_coverage = __esm({
4433
4475
  "../shared/dist/expected-outcome-coverage.js"() {
4434
4476
  "use strict";
@@ -4465,6 +4507,7 @@ var init_expected_outcome_coverage = __esm({
4465
4507
  ]);
4466
4508
  MAX_ASSERTION_JOIN_CHARS = 8e3;
4467
4509
  MAX_COVERAGE_LINE_CHARS = 2e3;
4510
+ REGEX_PRECEDER2 = "(,=:[!&|?{};+-*%<>~^";
4468
4511
  BLOCK_SCAFFOLD = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
4469
4512
  cachedAssertionInput = null;
4470
4513
  cachedAssertionSource = { raw: "", normalized: "", bindings: /* @__PURE__ */ new Map(), bindingRefs: /* @__PURE__ */ new Map() };
@@ -4915,7 +4958,7 @@ function detectsErrorBannerAssertion(code) {
4915
4958
  const cleaned = stripCommentsAndLogs(stripLoginScaffolding(code));
4916
4959
  const lines = cleaned.split("\n");
4917
4960
  const candidates = [];
4918
- const positive = (stmt) => !NEGATED_ASSERT.test(stmt) && !ABSENCE_MATCHER.test(stmt);
4961
+ const positive = (stmt) => !claimsAbsence(stmt);
4919
4962
  const statements = liveExpectStatements(cleaned).map((s) => s.stmt);
4920
4963
  for (const stmt of statements) {
4921
4964
  if (positive(stmt))
@@ -5044,6 +5087,12 @@ function detectsObservedFailureAsExpected(code) {
5044
5087
  return FAILURE_STATUS_ASSERT.some((re) => re.test(stmt)) || hasFailedListener && FAILED_LIST_ASSERT.test(stmt);
5045
5088
  });
5046
5089
  }
5090
+ function claimsAbsence(stmt) {
5091
+ const negated = NEGATED_ASSERT_NOT.test(stmt);
5092
+ const bare = negated ? stmt.replace(NOT_BEFORE_MATCHER, ".") : stmt;
5093
+ const absenceMatcher = /\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/.test(bare) || VALUE_ABSENCE.test(bare) || OPTION_ABSENCE.test(bare);
5094
+ return negated !== absenceMatcher;
5095
+ }
5047
5096
  function isVacuousErrorAbsence(stmt, presence) {
5048
5097
  if (!ABSENCE_MATCHER.test(stmt))
5049
5098
  return false;
@@ -5059,19 +5108,53 @@ function isChromeTarget(stmt) {
5059
5108
  return CHROME_LOCATOR.test(stmt) && !CONTENT_ROLE.test(stmt) && !/getByText\s*\(/.test(stmt);
5060
5109
  }
5061
5110
  function countsAsContentText(stmt) {
5062
- if (!TEXT_CONTENT_MATCHER.test(stmt))
5111
+ if (!TEXT_CONTENT_MATCHER.test(stmt) || NEGATED_ASSERT.test(stmt))
5063
5112
  return false;
5064
5113
  if (isSymbolOnlyArgument(stmt, TEXT_CONTENT_MATCHER))
5065
5114
  return false;
5066
- if (!isChromeTarget(stmt))
5115
+ return onContentTarget(stmt);
5116
+ }
5117
+ function onContentTarget(stmt) {
5118
+ return !isChromeTarget(stmt) || /getByRole\s*\(\s*['"`]main['"`]/i.test(stmt);
5119
+ }
5120
+ function provesTextRemoved(stmt, presence) {
5121
+ const m = stmt.match(NEGATED_TEXT);
5122
+ if (!m || !onContentTarget(stmt))
5123
+ return false;
5124
+ const text = m[1] ?? m[2];
5125
+ if (text === void 0)
5067
5126
  return true;
5068
- return /getByRole\s*\(\s*['"`]main['"`]/i.test(stmt);
5127
+ if (!/[\p{L}\p{N}]/u.test(text))
5128
+ return false;
5129
+ return !ERROR_TEXT_WORDS.test(text) || presence().includes(text);
5069
5130
  }
5070
5131
  function isDataContentRole(stmt) {
5071
5132
  return CONTENT_ROLE.test(stmt) && !ANNOUNCE_ROLE.test(stmt);
5072
5133
  }
5134
+ function destructuresRequest(code) {
5135
+ let readTo = -1;
5136
+ for (const m of code.matchAll(ASYNC_DESTRUCTURE_OPEN)) {
5137
+ const start = m.index + m[0].length;
5138
+ if (start <= readTo)
5139
+ continue;
5140
+ let end = start;
5141
+ while (end < code.length && code[end] !== "}" && code[end] !== ")")
5142
+ end++;
5143
+ readTo = end;
5144
+ if (end === code.length)
5145
+ return false;
5146
+ if (code[end] !== "}" || !REQUEST_WORD.test(code.slice(start, end)))
5147
+ continue;
5148
+ let close = end + 1;
5149
+ while (close < code.length && /\s/.test(code[close]))
5150
+ close++;
5151
+ if (code[close] === ")")
5152
+ return true;
5153
+ }
5154
+ return false;
5155
+ }
5073
5156
  function isApiTest(code) {
5074
- const usesRequestFixture = /async\s*\(\s*\{[^})]*\brequest\b[^})]*\}\s*\)/.test(code) || /\brequest\.(get|post|put|patch|delete|head|fetch)\s*\(/.test(code);
5157
+ const usesRequestFixture = destructuresRequest(code) || /\brequest\.(get|post|put|patch|delete|head|fetch)\s*\(/.test(code);
5075
5158
  if (!usesRequestFixture)
5076
5159
  return false;
5077
5160
  const drivesUi = /\bpage\.(getBy\w+|locator|click|fill|goto|waitForURL|reload)\s*\(/.test(code) || /\.(click|fill|check|uncheck|selectOption|pressSequentially)\s*\(/.test(code);
@@ -5330,6 +5413,9 @@ function hasOnlyConstantAssertions(code) {
5330
5413
  const { constant, real } = constantAssertions(code);
5331
5414
  return constant.length > 0 && real === 0;
5332
5415
  }
5416
+ function envValueKey(name) {
5417
+ return `\0env:${name}`;
5418
+ }
5333
5419
  function enteredValues(stmts) {
5334
5420
  const values = /* @__PURE__ */ new Map();
5335
5421
  const navLines = [];
@@ -5338,24 +5424,55 @@ function enteredValues(stmts) {
5338
5424
  navLines.push(startLine);
5339
5425
  let m;
5340
5426
  ENTERED_VALUE.lastIndex = 0;
5341
- while (m = ENTERED_VALUE.exec(stmt)) {
5342
- const value = (m[1] ?? m[2] ?? m[3]).toLowerCase();
5343
- if (!values.has(value))
5344
- values.set(value, startLine);
5345
- }
5427
+ const add = (value) => {
5428
+ const lines = values.get(value);
5429
+ if (!lines)
5430
+ values.set(value, [startLine]);
5431
+ else if (lines[lines.length - 1] !== startLine)
5432
+ lines.push(startLine);
5433
+ };
5434
+ while (m = ENTERED_VALUE.exec(stmt))
5435
+ add((m[1] ?? m[2] ?? m[3]).toLowerCase());
5436
+ ENTERED_ENV.lastIndex = 0;
5437
+ while (m = ENTERED_ENV.exec(stmt))
5438
+ add(envValueKey(m[1] ?? m[2] ?? m[3] ?? m[4]));
5346
5439
  }
5347
5440
  return { values, navLines };
5348
5441
  }
5349
- function isEnteredValueEcho(stmt, entered, startLine) {
5442
+ function countBelow(lines, line) {
5443
+ let lo = 0;
5444
+ let hi = lines.length;
5445
+ while (lo < hi) {
5446
+ const mid = lo + hi >> 1;
5447
+ if (lines[mid] < line)
5448
+ lo = mid + 1;
5449
+ else
5450
+ hi = mid;
5451
+ }
5452
+ return lo;
5453
+ }
5454
+ function enteredLinesOf(stmt, entered) {
5350
5455
  const m = stmt.match(/\.toHaveValue\s*\(\s*(['"`])((?:(?!\1).)+)\1/);
5351
- if (!m)
5456
+ if (m)
5457
+ return entered.values.get(m[2].toLowerCase());
5458
+ const env = stmt.match(HAVE_VALUE_ENV);
5459
+ return env ? entered.values.get(envValueKey(env[1] ?? env[2])) : void 0;
5460
+ }
5461
+ function typedSinceLastNav(stmt, entered, startLine, lines = enteredLinesOf(stmt, entered)) {
5462
+ if (!lines || startLine === void 0)
5352
5463
  return false;
5353
- const enteredAt = entered.values.get(m[2].toLowerCase());
5354
- if (enteredAt === void 0)
5464
+ const before = countBelow(lines, startLine);
5465
+ if (before === 0)
5355
5466
  return false;
5356
- if (startLine === void 0)
5467
+ return countBelow(entered.navLines, startLine) === countBelow(entered.navLines, lines[before - 1] + 1);
5468
+ }
5469
+ function isEnteredValueEcho(stmt, entered, startLine) {
5470
+ const lines = enteredLinesOf(stmt, entered);
5471
+ if (!lines)
5472
+ return false;
5473
+ if (startLine === void 0 || lines[0] >= startLine)
5357
5474
  return true;
5358
- return !entered.navLines.some((n) => n > enteredAt && n < startLine);
5475
+ return typedSinceLastNav(stmt, entered, startLine, lines);
5359
5476
  }
5360
5477
  function isAnyValueMatcher(stmt) {
5361
5478
  if (/\.not\.toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/.test(stmt))
@@ -5445,6 +5562,43 @@ function hasAttributeTransition(stmts) {
5445
5562
  return true;
5446
5563
  return false;
5447
5564
  }
5565
+ function comparesWithItself(subject, tail) {
5566
+ const m = SELF_COMPARE_MATCHER.exec(tail);
5567
+ if (!m)
5568
+ return false;
5569
+ const args = balancedArguments(tail, m[0].length, MATCHER_ARGS_MAX);
5570
+ if (args === null)
5571
+ return false;
5572
+ const squash2 = (x) => x.replace(/^\s*await\s+/, "").replace(/\s+/g, "");
5573
+ const expected = squash2(args);
5574
+ return expected === squash2(m[1] === "toHaveCount" ? `${subject}.count()` : subject);
5575
+ }
5576
+ function asPlainAssertion(s) {
5577
+ const { stmt } = s;
5578
+ if (COUNT_AT_LEAST_ZERO.test(stmt))
5579
+ return [];
5580
+ const subject = extractExpectSubject(stmt);
5581
+ if (subject === null)
5582
+ return [s];
5583
+ const plain = subject.replace(/^await\s+/, "").trim();
5584
+ const subjectAt = stmt.indexOf(subject, stmt.search(EXPECT_CALL_RE));
5585
+ const tail = stmt.slice(subjectAt + subject.length).trimStart();
5586
+ const as = (rewritten) => [{ ...s, stmt: rewritten }];
5587
+ if (LOCATOR_EXPR.test(plain) && !LOCATOR_VALUE_READ.test(plain) && ALWAYS_TRUE_ON_OBJECT.test(tail))
5588
+ return [];
5589
+ if (DEFINED_VALUE_READ.test(plain) && ALWAYS_DEFINED.test(tail))
5590
+ return [];
5591
+ if (comparesWithItself(plain, tail))
5592
+ return [];
5593
+ const visible = /^(.+)\.\s*isVisible\s*\(\s*\)$/s.exec(plain);
5594
+ if (visible && IS_VISIBLE_TRUE.test(tail))
5595
+ return as(`await expect(${visible[1]}).toBeVisible();`);
5596
+ if (/^page\s*\.\s*(?:url|title)\s*\(\s*\)$/.test(plain) && PAGE_VALUE_MATCHER.test(tail)) {
5597
+ const matcher = plain.includes("url") ? "toHaveURL" : "toHaveTitle";
5598
+ return as(`await expect(page)${tail.replace(/^\)\s*\.\s*\w+\s*\(/, `.${matcher}(`)}`);
5599
+ }
5600
+ return [s];
5601
+ }
5448
5602
  function creditsOutcomeAlways(stmt, entered, startLine) {
5449
5603
  if (!OUTCOME_ALWAYS.some((re) => re.test(stmt)))
5450
5604
  return false;
@@ -5518,6 +5672,8 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
5518
5672
  continue;
5519
5673
  if (isVacuousErrorAbsence(stmt, presence))
5520
5674
  continue;
5675
+ if (COUNT_ONE.test(stmt) && quotedAssertedStrings(stmt).some((t) => clicked.has(t.toLowerCase())))
5676
+ continue;
5521
5677
  if (creditsOutcomeAlways(stmt, entered, startLine))
5522
5678
  return { kind: "durable" };
5523
5679
  if (mutated && !NEGATED_ASSERT.test(stmt) && MESSAGE_SURFACE.test(stmt) && OUTCOME_TEXT.test(assertedText(stmt))) {
@@ -5527,12 +5683,15 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
5527
5683
  else
5528
5684
  return { kind: "durable" };
5529
5685
  }
5530
- if (STATE_MATCHER.test(stmt) && (persistence && startLine !== void 0 ? reopenedAfterSave(startLine, persistence, persistence.reloadCalls) : reloaded))
5686
+ const retyped = typedSinceLastNav(stmt, entered, startLine);
5687
+ if (!retyped && STATE_MATCHER.test(stmt) && (persistence && startLine !== void 0 ? reopenedAfterSave(startLine, persistence, persistence.reloadCalls) : reloaded))
5531
5688
  return { kind: "durable" };
5532
- if (provesStoredControlValue(stmt, startLine, persistence))
5689
+ if (!retyped && provesStoredControlValue(stmt, startLine, persistence))
5533
5690
  return { kind: "durable" };
5534
5691
  if (countsAsContentText(stmt))
5535
5692
  return { kind: "durable" };
5693
+ if (provesTextRemoved(stmt, presence))
5694
+ return { kind: "durable" };
5536
5695
  if (mutated && VISIBILITY_MATCHER.test(stmt)) {
5537
5696
  const revealsText = GET_BY_TEXT.test(stmt) || HAS_TEXT_FILTER.test(stmt) || CONTENT_ROLE.test(stmt);
5538
5697
  if (revealsText && !isChromeTarget(stmt)) {
@@ -5599,7 +5758,7 @@ function detectOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMut
5599
5758
  function validateAssertionStrength(code, opts = {}) {
5600
5759
  const body = stripLoginScaffolding(code);
5601
5760
  const lines = body.split("\n");
5602
- const stmts = liveExpectStatements(stripCoverageComments(body));
5761
+ const stmts = liveExpectStatements(stripCoverageComments(body)).flatMap(asPlainAssertion);
5603
5762
  const base = (extra) => ({
5604
5763
  apiTest: false,
5605
5764
  interacted: false,
@@ -5805,7 +5964,7 @@ function validateAssertionStrength(code, opts = {}) {
5805
5964
  signals
5806
5965
  };
5807
5966
  }
5808
- var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, IN_FLIGHT_TEXT, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD;
5967
+ var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, NEGATED_ASSERT_NOT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, VALUE_ABSENCE, OPTION_ABSENCE, NOT_BEFORE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, NEGATED_TEXT, ASYNC_DESTRUCTURE_OPEN, REQUEST_WORD, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, ENV_READ, ENTERED_ENV, HAVE_VALUE_ENV, IN_FLIGHT_TEXT, LOCATOR_VALUE_READ, LOCATOR_EXPR, ALWAYS_TRUE_ON_OBJECT, COUNT_AT_LEAST_ZERO, DEFINED_VALUE_READ, ALWAYS_DEFINED, SELF_COMPARE_MATCHER, IS_VISIBLE_TRUE, PAGE_VALUE_MATCHER, COUNT_ONE, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD;
5809
5968
  var init_assertion_strength = __esm({
5810
5969
  "../shared/dist/assertion-strength.js"() {
5811
5970
  "use strict";
@@ -5830,8 +5989,8 @@ var init_assertion_strength = __esm({
5830
5989
  // form field value
5831
5990
  /\.toMatch\s*\(/,
5832
5991
  // string/regex content match (e.g. textContent() vs /[2-9] selected/)
5833
- /\.toBeDisabled\s*\(/,
5834
- // a control became DISABLED — a gating/validation outcome
5992
+ /(?<!\.not\s*)\.\s*toBeDisabled\s*\(/,
5993
+ // a control became DISABLED — a gating/validation outcome (not.toBeDisabled is the toBeEnabled twin)
5835
5994
  /\.not\.toBeVisible\s*\(/,
5836
5995
  /\.toBeHidden\s*\(/
5837
5996
  // something the action removed/closed (disappearance proof)
@@ -5839,7 +5998,7 @@ var init_assertion_strength = __esm({
5839
5998
  FORM_INPUT = /\.(fill|selectOption|setInputFiles|check|uncheck|setChecked|pressSequentially)\s*\(|keyboard\.type\s*\(/;
5840
5999
  ANY_CLICK = /\.click\s*\(/;
5841
6000
  NAV_LOCATOR = /getByRole\s*\(\s*['"`](?:link|menuitem)['"`]/i;
5842
- URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(/;
6001
+ URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(|\bexpect\s*\(\s*(?:await\s+)?page\s*\.\s*url\s*\(\s*\)\s*\)|\bexpect\s*\.\s*poll\s*\(\s*(?:async\s*)?\(\s*\)\s*=>\s*(?:\{\s*return\s+)?page\s*\.\s*url\s*\(\s*\)/;
5843
6002
  RELOAD = /\.reload\s*\(/;
5844
6003
  EXPECT_STMT = /\bexpect\s*[.(]/;
5845
6004
  BLOCK_SCAFFOLD2 = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
@@ -5868,6 +6027,7 @@ var init_assertion_strength = __esm({
5868
6027
  INBOX_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_INBOX/;
5869
6028
  INBOX_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_INBOX/;
5870
6029
  NEGATED_ASSERT = /\.not\s*\.\s*to[A-Z]\w*|\.toBeHidden\s*\(/;
6030
+ NEGATED_ASSERT_NOT = /\.not\s*\.\s*to[A-Z]/;
5871
6031
  CONST_CHAIN_MAX_DEPTH = 5;
5872
6032
  TYPED_INPUT = /\.(fill|type|pressSequentially|setInputFiles)\s*\(/;
5873
6033
  TITLE_LINE = /^\s*(?:test|describe)(?:\.\w+)?\s*\(/;
@@ -5883,8 +6043,14 @@ var init_assertion_strength = __esm({
5883
6043
  CATCH_ALL_PATTERN = /^(['"`])\*\*(?:\/\*)?\1$|^\/\.\*\/[a-z]*$/;
5884
6044
  GUARD_HANDLER = /\bMUTATING\.has\s*\(|blockedbyclient|Read-only guard/;
5885
6045
  ABSENCE_MATCHER = /\.not\.toBeVisible\s*\(|\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/;
6046
+ VALUE_ABSENCE = /\.(?:count\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*0\s*\)|toBeLessThan\s*\(\s*1\s*\))|isVisible\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*false\s*\)|toBeFalsy\s*\()|isHidden\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*true\s*\)|toBeTruthy\s*\())/;
6047
+ OPTION_ABSENCE = /\.(?:toBeVisible\s*\(\s*\{[^{}()]{0,200}\bvisible|toBeAttached\s*\(\s*\{[^{}()]{0,200}\battached)\s*:\s*false\b/;
6048
+ NOT_BEFORE_MATCHER = /\.not\s*\.\s*(?=to[A-Z])/g;
5886
6049
  ERROR_TEXT_WORDS = /error|fail|wrong|try again|unable|could ?n.?t|can.?t|problem|invalid|unavailable/i;
5887
6050
  TEXT_REGEX_ARG = /(?:getByText|hasText|getByRole\([^)]{0,200}name)\s*[:(]\s*(\/(?:\\.|[^/\\\n])+\/[a-z]*)/;
6051
+ NEGATED_TEXT = /\.not\s*\.\s*(?:toHaveText|toContainText)\s*\(\s*(?:['"`]([^'"`]*)['"`]|\/((?:\\.|[^/\\\n])+)\/)?/;
6052
+ ASYNC_DESTRUCTURE_OPEN = /async\s*\(\s*\{/g;
6053
+ REQUEST_WORD = /\brequest\b/;
5888
6054
  ROLE_NAME_RE = /getByRole\s*\(\s*['"`][a-z]+['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
5889
6055
  INTERACT_VERB = /\.(click|fill|check|uncheck|setChecked|selectOption|pressSequentially)\s*\(/;
5890
6056
  ROLE_SIG_RE = /getByRole\s*\(\s*['"`]([a-z]+)['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
@@ -5905,7 +6071,20 @@ var init_assertion_strength = __esm({
5905
6071
  MATCHER_OPEN = /\s*(?:\.\s*(?:not|resolves|rejects)\s*)*\.\s*[A-Za-z_$][\w$]*\s*\(/y;
5906
6072
  ENTERED_VALUE = /\.(?:fill|pressSequentially)\s*\(\s*['"`]([^'"`]+)['"`]|keyboard\.type\s*\(\s*['"`]([^'"`]+)['"`]|\.selectOption\s*\(\s*(?:\[\s*)?(?:\{\s*(?:label|value)\s*:\s*)?['"`]([^'"`]+)['"`]/g;
5907
6073
  PAGE_NAV = /\.(?:goto|reload)\s*\(/;
6074
+ ENV_READ = String.raw`process\.env(?:(?:\?\.|\.)([A-Za-z_]\w*)|(?:\?\.)?\[\s*['"]([A-Za-z_]\w*)['"]\s*\])`;
6075
+ ENTERED_ENV = new RegExp(String.raw`\.(?:fill|pressSequentially)\s*\(\s*${ENV_READ}|keyboard\.type\s*\(\s*${ENV_READ}`, "g");
6076
+ HAVE_VALUE_ENV = new RegExp(String.raw`\.toHaveValue\s*\(\s*${ENV_READ}`);
5908
6077
  IN_FLIGHT_TEXT = /^(?:(?:sending|saving|loading|searching|checking|processing|submitting|preparing|updating|please wait|working|one moment)\b[^.]{0,40}\.{2,3}|\.{3}|…)$/i;
6078
+ LOCATOR_VALUE_READ = /\.(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts|evaluate|evaluateAll|boundingBox|all)\s*\(/;
6079
+ LOCATOR_EXPR = /^(?:this\.)?page\s*\.\s*(?:getBy\w+|locator)\s*\(/;
6080
+ ALWAYS_TRUE_ON_OBJECT = /^\)\s*\.\s*(?:toBeTruthy|toBeDefined|not\s*\.\s*(?:toBeNull|toBeUndefined|toBeFalsy))\s*\(/;
6081
+ COUNT_AT_LEAST_ZERO = /\.(?:count\s*\(\s*\)|length)[\s)]{0,10}\.(?:toBeGreaterThanOrEqual\s*\(\s*0\s*\)|toBeGreaterThan\s*\(\s*-\s*1\s*\))/;
6082
+ DEFINED_VALUE_READ = /\.\s*(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|title|url|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts)\s*\([^()]*\)$/;
6083
+ ALWAYS_DEFINED = /^\)\s*\.\s*(?:toBeDefined|not\s*\.\s*toBeUndefined)\s*\(/;
6084
+ SELF_COMPARE_MATCHER = /^\)\s*\.\s*(toBe|toEqual|toStrictEqual|toHaveCount)\s*\(/;
6085
+ IS_VISIBLE_TRUE = /^\)*\s*\.\s*(?:toBe\s*\(\s*true\s*\)|toBeTruthy\s*\(\s*\))/;
6086
+ PAGE_VALUE_MATCHER = /^\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
6087
+ COUNT_ONE = /\.\s*toHaveCount\s*\(\s*1\s*\)/;
5909
6088
  FORM_CONTROL_LOCATOR = /getByRole\s*\(\s*['"`](?:combobox|switch|checkbox|radio|textbox|searchbox|spinbutton|slider)['"`]|getByLabel\s*\(|getByPlaceholder\s*\(/;
5910
6089
  CONTROL_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(|\.toHaveAttribute\s*\(\s*['"`]aria-checked['"`]/;
5911
6090
  COPY_CONTROL = /\bcopy\b/i;
@@ -6114,7 +6293,7 @@ function reportedBugWhere() {
6114
6293
  function bugReviewStatus(status) {
6115
6294
  return status === "QUARANTINED" ? "confirmed" : "needs-review";
6116
6295
  }
6117
- var USER_CANCELLED_RUN_ERROR, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
6296
+ var USER_CANCELLED_RUN_ERROR, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, TEST_DID_NOT_RUN_ERROR, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
6118
6297
  var init_run_classification = __esm({
6119
6298
  "../shared/dist/run-classification.js"() {
6120
6299
  "use strict";
@@ -6134,6 +6313,7 @@ var init_run_classification = __esm({
6134
6313
  UNEXPECTED_PASS_ERROR_PREFIX = "UNEXPECTED_PASS:";
6135
6314
  BUG_TEST_BROKEN_ERROR_PREFIX = "BUG_TEST_BROKEN:";
6136
6315
  BUG_TEST_BROKEN_REASON = "bug test broken (not a bug verdict)";
6316
+ TEST_DID_NOT_RUN_ERROR = "Test did not run (a dependency failed or the run was interrupted)";
6137
6317
  EMAIL_NOT_RECEIVED_PREFIXES = [
6138
6318
  "VERIFICATION_EMAIL_NOT_RECEIVED:",
6139
6319
  "VERIFICATION_EMAIL_HAS_NO_CODE:",
@@ -8875,6 +9055,23 @@ var init_auth_block_detector = __esm({
8875
9055
  }
8876
9056
  });
8877
9057
 
9058
+ // ../shared/dist/auth-session-stamp.js
9059
+ function parseAuthSessionStamp(value) {
9060
+ if (!value || typeof value !== "object" || Array.isArray(value))
9061
+ return null;
9062
+ const { shared, role, generation } = value;
9063
+ if (shared !== true || role !== "producer" && role !== "follower")
9064
+ return null;
9065
+ return typeof generation === "string" && AUTH_SESSION_GENERATION_RE.test(generation) ? { shared: true, role, generation } : { shared: true, role };
9066
+ }
9067
+ var AUTH_SESSION_GENERATION_RE;
9068
+ var init_auth_session_stamp = __esm({
9069
+ "../shared/dist/auth-session-stamp.js"() {
9070
+ "use strict";
9071
+ AUTH_SESSION_GENERATION_RE = /^[0-9a-z]{1,16}$/;
9072
+ }
9073
+ });
9074
+
8878
9075
  // ../shared/dist/auth-state-freshness.js
8879
9076
  function isCsrfTokenName(name) {
8880
9077
  return /csrf|xsrf|forgery|authenticity|requestverification/.test(name.toLowerCase());
@@ -10103,7 +10300,7 @@ var init_agentic_cli_runtime = __esm({
10103
10300
  GROK_CLI_DEFAULT_MODEL = "grok-4.7";
10104
10301
  GROK_CLI_DEFAULT_REASONING_EFFORT = "high";
10105
10302
  CLAUDE_CLI_DEFAULT_MODEL = "claude-sonnet-5-5";
10106
- CLAUDE_CLI_DEFAULT_EFFORT = "max";
10303
+ CLAUDE_CLI_DEFAULT_EFFORT = "high";
10107
10304
  MUSE_CLI_DEFAULT_MODEL = "muse-spark-1.3-contributor";
10108
10305
  MUSE_CLI_DEFAULT_EFFORT = "high";
10109
10306
  CODEX_CLI_DEFAULT_MODEL = "gpt-6.1-sol";
@@ -10154,9 +10351,25 @@ function isOpenCodeGoModelId(id) {
10154
10351
  function hasOpenCodeGoPrefix(id) {
10155
10352
  return (id?.trim() ?? "").startsWith(OPENCODE_GO_PREFIX);
10156
10353
  }
10354
+ function hasOpenCodeGo2Prefix(id) {
10355
+ return (id?.trim() ?? "").startsWith(OPENCODE_GO_2_PREFIX);
10356
+ }
10157
10357
  function openCodeGoWireModel(id) {
10158
10358
  const trimmed = id?.trim() ?? "";
10159
- return isOpenCodeGoModelId(trimmed) ? trimmed.slice(OPENCODE_GO_PREFIX.length) : null;
10359
+ if (!isOpenCodeGoModelId(trimmed))
10360
+ return null;
10361
+ return trimmed.slice((hasOpenCodeGo2Prefix(trimmed) ? OPENCODE_GO_2_PREFIX : OPENCODE_GO_PREFIX).length);
10362
+ }
10363
+ function isZaiModelId(id) {
10364
+ const trimmed = id?.trim() ?? "";
10365
+ return ZAI_MODEL_PATTERN.test(trimmed) && !trimmed.includes("..");
10366
+ }
10367
+ function hasZaiPrefix(id) {
10368
+ return (id?.trim() ?? "").startsWith(ZAI_PREFIX);
10369
+ }
10370
+ function zaiWireModel(id) {
10371
+ const trimmed = id?.trim() ?? "";
10372
+ return isZaiModelId(trimmed) ? trimmed.slice(ZAI_PREFIX.length) : null;
10160
10373
  }
10161
10374
  function isEngineId(value) {
10162
10375
  return CLI_ENGINE_MODEL_IDS.includes(value);
@@ -10254,7 +10467,7 @@ function museCliContributorMode(env = process.env) {
10254
10467
  const raw = (env.MUSE_CLI_CONTRIBUTOR_MODE ?? "").trim().toLowerCase();
10255
10468
  return raw === "off" || raw === "false" || raw === "0" || raw === "no" ? "off" : "on";
10256
10469
  }
10257
- var CLI_ENGINE_MODEL_IDS, BROWSER_CLI_ENGINE_IDS, PIPELINE_STEPS, CLI_ENGINE_RULES, ENGINE_NAMES, OPENROUTER_PREFIX, OPENROUTER_MODEL_PATTERN, OPENCODE_GO_PREFIX, OPENCODE_GO_MODEL_PATTERN;
10470
+ var CLI_ENGINE_MODEL_IDS, BROWSER_CLI_ENGINE_IDS, PIPELINE_STEPS, CLI_ENGINE_RULES, ENGINE_NAMES, OPENROUTER_PREFIX, OPENROUTER_MODEL_PATTERN, OPENCODE_GO_PREFIX, OPENCODE_GO_2_PREFIX, OPENCODE_GO_MODEL_PATTERN, ZAI_PREFIX, ZAI_MODEL_PATTERN;
10258
10471
  var init_engine_spec = __esm({
10259
10472
  "../shared/dist/engine-spec.js"() {
10260
10473
  "use strict";
@@ -10313,7 +10526,10 @@ var init_engine_spec = __esm({
10313
10526
  OPENROUTER_PREFIX = "openrouter:";
10314
10527
  OPENROUTER_MODEL_PATTERN = /^openrouter:[a-z0-9][a-z0-9._-]{0,40}\/[A-Za-z0-9._:-]{1,100}$/;
10315
10528
  OPENCODE_GO_PREFIX = "opencode-go:";
10316
- OPENCODE_GO_MODEL_PATTERN = /^opencode-go:[a-z0-9][a-z0-9._-]{0,100}$/;
10529
+ OPENCODE_GO_2_PREFIX = "opencode-go-2:";
10530
+ OPENCODE_GO_MODEL_PATTERN = /^opencode-go(?:-2)?:[a-z0-9][a-z0-9._-]{0,100}$/;
10531
+ ZAI_PREFIX = "zai:";
10532
+ ZAI_MODEL_PATTERN = /^zai:glm-[a-z0-9][a-z0-9._-]{0,60}$/;
10317
10533
  }
10318
10534
  });
10319
10535
 
@@ -10598,6 +10814,8 @@ var init_api_model_catalog = __esm({
10598
10814
  authCaptureModel: "Login capture",
10599
10815
  plannerModel: "Scenario Planner",
10600
10816
  plannerGateModel: "Planner Gate",
10817
+ pwAgentsHealerModel: "Test Healer (Playwright agents)",
10818
+ pwAgentsReviewModel: "Plan Review (Playwright agents)",
10601
10819
  generationModel: "Code Generation",
10602
10820
  reviewModel: "Code Review",
10603
10821
  apiTestModel: "API Test Generator",
@@ -10622,11 +10840,6 @@ var init_api_model_catalog = __esm({
10622
10840
  });
10623
10841
 
10624
10842
  // ../shared/dist/provider-chains.js
10625
- function isClaudeModel(id) {
10626
- if (/claude|anthropic/i.test(id))
10627
- return true;
10628
- return API_MODEL_CATALOG.some((m) => m.provider === "anthropic" && (m.id === id || m.apiModel === id));
10629
- }
10630
10843
  function chainKey(modelId) {
10631
10844
  const id = canonicalModelId(modelId);
10632
10845
  if (!id)
@@ -10634,34 +10847,83 @@ function chainKey(modelId) {
10634
10847
  const entry = API_MODEL_CATALOG.find((m) => m.id === id || m.apiModel === id);
10635
10848
  return entry?.id ?? id;
10636
10849
  }
10850
+ function has(map, key) {
10851
+ return Object.prototype.hasOwnProperty.call(map, key);
10852
+ }
10637
10853
  function providerChainFor(modelId) {
10638
10854
  const key = chainKey(modelId);
10639
- if (!key || isClaudeModel(key))
10640
- return [];
10641
- return Object.prototype.hasOwnProperty.call(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
10855
+ return key && has(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
10856
+ }
10857
+ function providerChainLeadsFor(modelId) {
10858
+ const key = chainKey(modelId);
10859
+ return key && has(PROVIDER_CHAIN_LEADS, key) ? PROVIDER_CHAIN_LEADS[key] : [];
10860
+ }
10861
+ function allChains() {
10862
+ return [...Object.entries(PROVIDER_CHAINS), ...Object.entries(PROVIDER_CHAIN_LEADS)];
10642
10863
  }
10643
10864
  function isProviderChainHopId(modelId) {
10644
10865
  const id = modelId ?? "";
10645
- return id !== "" && Object.values(PROVIDER_CHAINS).some((hops) => hops.includes(id));
10866
+ return id !== "" && allChains().some(([, hops]) => hops.includes(id));
10646
10867
  }
10647
10868
  function providerChainPrimariesForHop(hopId) {
10648
10869
  const id = hopId ?? "";
10649
10870
  if (!id)
10650
10871
  return [];
10651
- return Object.entries(PROVIDER_CHAINS).filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
10872
+ return allChains().filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
10873
+ }
10874
+ function providerChainHostOf(value, modelValue) {
10875
+ if (value === modelValue)
10876
+ return "own";
10877
+ if (hasZaiPrefix(value))
10878
+ return "zai";
10879
+ if (hasOpenCodeGo2Prefix(value))
10880
+ return "opencode-go-2";
10881
+ if (hasOpenCodeGoPrefix(value))
10882
+ return "opencode-go";
10883
+ if (hasOpenRouterPrefix(value))
10884
+ return "openrouter";
10885
+ return "own";
10886
+ }
10887
+ function providerHostPlan(modelId) {
10888
+ const key = chainKey(modelId);
10889
+ if (!key)
10890
+ return [];
10891
+ const own = { host: hasOpenRouterPrefix(key) ? "openrouter" : "own", value: key };
10892
+ const steps = [
10893
+ ...providerChainLeadsFor(key).map((value) => ({ host: providerChainHostOf(value, key), value })),
10894
+ own,
10895
+ ...providerChainFor(key).map((value) => ({ host: providerChainHostOf(value, key), value }))
10896
+ ];
10897
+ const notOffered = Object.prototype.hasOwnProperty.call(PROVIDER_HOST_NOT_OFFERED, key) ? PROVIDER_HOST_NOT_OFFERED[key] : {};
10898
+ for (const [host, reason] of Object.entries(notOffered)) {
10899
+ const at = steps.findIndex((step) => step === own) + 1;
10900
+ steps.splice(at, 0, { host, value: null, notOffered: reason });
10901
+ }
10902
+ return steps;
10652
10903
  }
10653
- var PROVIDER_CHAINS;
10904
+ var PROVIDER_CHAINS, PROVIDER_CHAIN_LEADS, PROVIDER_HOST_NOT_OFFERED;
10654
10905
  var init_provider_chains = __esm({
10655
10906
  "../shared/dist/provider-chains.js"() {
10656
10907
  "use strict";
10657
10908
  init_api_model_catalog();
10909
+ init_engine_spec();
10658
10910
  PROVIDER_CHAINS = {
10659
- "minimax-m3": ["openrouter:minimax/minimax-m3", "opencode-go:minimax-m3"],
10660
- "minimax-m3.1-flash": ["openrouter:minimax/minimax-m3.1-flash", "opencode-go:minimax-m3.1-flash"],
10911
+ "minimax-m3": ["opencode-go:minimax-m3", "openrouter:minimax/minimax-m3"],
10912
+ "minimax-m3.1-flash": ["opencode-go:minimax-m3.1-flash", "openrouter:minimax/minimax-m3.1-flash"],
10661
10913
  "muse-spark-1.3-contributor": ["openrouter:meta/muse-spark-1.3-contributor"],
10662
- "openrouter:z-ai/glm-5.3-flash": ["opencode-go:glm-5.3-flash"],
10663
- "openrouter:z-ai/glm-5.3": ["opencode-go:glm-5.3"],
10664
- "openrouter:z-ai/glm-5.2": ["opencode-go:glm-5.2"]
10914
+ "claude-haiku-5-5": ["opencode-go:claude-haiku-5-5", "openrouter:anthropic/claude-haiku-5.5"],
10915
+ "gemini-3.1-pro-preview": ["openrouter:google/gemini-3.1-pro-preview"],
10916
+ "deepseek-flash": ["opencode-go-2:deepseek-v4.1-flash", "opencode-go:deepseek-v4.1-flash", "openrouter:deepseek/deepseek-v4.1-flash"],
10917
+ "deepseek-v4-pro": ["opencode-go-2:deepseek-v4-pro", "opencode-go:deepseek-v4-pro", "openrouter:deepseek/deepseek-v4-pro"]
10918
+ };
10919
+ PROVIDER_CHAIN_LEADS = {
10920
+ "openrouter:z-ai/glm-5.3-flash": ["zai:glm-5.3-flash", "opencode-go-2:glm-5.3-flash", "opencode-go:glm-5.3-flash"],
10921
+ "openrouter:z-ai/glm-5.3": ["zai:glm-5.3", "opencode-go-2:glm-5.3", "opencode-go:glm-5.3"],
10922
+ "openrouter:z-ai/glm-5.2": ["zai:glm-5.2", "opencode-go-2:glm-5.2", "opencode-go:glm-5.2"]
10923
+ };
10924
+ PROVIDER_HOST_NOT_OFFERED = {
10925
+ "muse-spark-1.3-contributor": { "opencode-go": "OpenCode Go serves Muse only on its Responses API" },
10926
+ "gemini-3.1-pro-preview": { "opencode-go": "OpenCode Go does not serve Gemini" }
10665
10927
  };
10666
10928
  }
10667
10929
  });
@@ -12620,7 +12882,7 @@ function stateLiteralsOf(line) {
12620
12882
  const out = [];
12621
12883
  for (const m of line.matchAll(STRING_LITERAL_RE)) {
12622
12884
  const text = m[1] ?? m[2] ?? m[3] ?? "";
12623
- if (text.includes("placeholder="))
12885
+ if (PLACEHOLDER_ATTR_RE.test(text))
12624
12886
  continue;
12625
12887
  if (PLACEHOLDER_CALL_TAIL_RE.test(line.slice(Math.max(0, m.index - 40), m.index)))
12626
12888
  continue;
@@ -12716,7 +12978,7 @@ function formatReadinessPollAdvisory(findings) {
12716
12978
  const where = findings.length === 1 ? "One place" : `${findings.length} places`;
12717
12979
  return `${where} re-check a background state once and assert the finished state at once (${short(first.line, 90)} then ${short(first.assertion, 90)}). ${first.reason}`;
12718
12980
  }
12719
- var READINESS_POLL_MAX_CODE_CHARS, MAX_LINE_CHARS, MAX_FINDINGS, ASSERTION_WINDOW_LINES, MAX_BLOCK_CHARS, MAX_HEAD_CHARS, STRING_LITERAL_RE, RECHECK_NAME_RE, RETRY_NAME_RE, PENDING_STATE_RE, LOCATOR_LINE_RE, CLICK_RE, RELOAD_RE, GOTO_RE, ACTION_RE, EXPECT_RE, NEGATED_RE, PENDING_ENDED_RE, POSITIVE_MATCHER_RE, ZERO_COUNT_RE, TEST_END_RE, TEST_START_RE, POLL_OPEN_RE, PLACEHOLDER_CALL_TAIL_RE, POLL_SHAPE;
12981
+ var READINESS_POLL_MAX_CODE_CHARS, MAX_LINE_CHARS, MAX_FINDINGS, ASSERTION_WINDOW_LINES, MAX_BLOCK_CHARS, MAX_HEAD_CHARS, STRING_LITERAL_RE, RECHECK_NAME_RE, RETRY_NAME_RE, PENDING_STATE_RE, LOCATOR_LINE_RE, CLICK_RE, RELOAD_RE, GOTO_RE, ACTION_RE, EXPECT_RE, NEGATED_RE, PENDING_ENDED_RE, POSITIVE_MATCHER_RE, ZERO_COUNT_RE, TEST_END_RE, TEST_START_RE, POLL_OPEN_RE, PLACEHOLDER_ATTR_RE, PLACEHOLDER_CALL_TAIL_RE, POLL_SHAPE;
12720
12982
  var init_readiness_poll = __esm({
12721
12983
  "../shared/dist/readiness-poll.js"() {
12722
12984
  "use strict";
@@ -12729,7 +12991,7 @@ var init_readiness_poll = __esm({
12729
12991
  STRING_LITERAL_RE = /'((?:\\.|[^'\\\n]){0,300})'|"((?:\\.|[^"\\\n]){0,300})"|`((?:\\.|[^`\\\n]){0,300})`/g;
12730
12992
  RECHECK_NAME_RE = /^\s{0,10}(?:check again|check (?:status|availability|now|for updates|progress)|re-?check(?: status)?|update status|see (?:latest|updates))\s{0,10}(?:\.\.\.|…)?\s{0,10}$/i;
12731
12993
  RETRY_NAME_RE = /^\s{0,10}(?:refresh(?: (?:status|list|page|results|now))?|reload(?: (?:page|results|list))?|try again|retry|load (?:again|latest))\s{0,10}(?:\.\.\.|…)?\s{0,10}$/i;
12732
- PENDING_STATE_RE = /\b(?:searching|checking|sending|pending|processing|queued|in progress|loading|generating|calculating|preparing|verifying|syncing|refreshing|not ready|please wait|waiting)\b|\b[a-z]{3,40}(?<!th)ing(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$/i;
12994
+ PENDING_STATE_RE = /\b(?:searching|checking|sending|pending|processing|queued|in progress|loading|generating|calculating|preparing|verifying|syncing|refreshing|not ready|please wait|waiting)\b|\b(?:saving|uploading|downloading|signing|logging|submitting|creating|updating|deleting|importing|exporting|publishing|connecting|redirecting|authenticating|installing|retrieving|fetching|analy[sz]ing|running|rendering|restarting|building|compiling|booting|provisioning|deploying|indexing|migrating|launching|initiali[sz]ing|optimi[sz]ing|converting|processing)(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$|^\s{0,10}(?:starting|scanning|working|applying|finishing)(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$|^\s{0,10}[a-z]{3,40}(?<!th)ing\s{0,3}(?:\.\.\.|…)\s{0,10}$/i;
12733
12995
  LOCATOR_LINE_RE = /\.(?:getBy[A-Z]\w{0,30}|locator|toHaveText|toContainText)\s{0,20}\(/;
12734
12996
  CLICK_RE = /\.(?:click|dblclick|tap)\s{0,20}\(/;
12735
12997
  RELOAD_RE = /\bpage\.reload\s{0,20}\(/;
@@ -12743,6 +13005,7 @@ var init_readiness_poll = __esm({
12743
13005
  TEST_END_RE = /^\s{0,20}\}\s{0,20}\)\s{0,20};?\s{0,20}$/;
12744
13006
  TEST_START_RE = /\btest(?:\.\w{1,20})?\s{0,20}\(/;
12745
13007
  POLL_OPEN_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}async\b|\bexpect\.poll\s{0,20}\(|\bfor\s{0,20}\(|\bwhile\s{0,20}\(|\bdo\s{0,20}\{/g;
13008
+ PLACEHOLDER_ATTR_RE = /placeholder\s{0,4}[*^$~|]?=/;
12746
13009
  PLACEHOLDER_CALL_TAIL_RE = /getByPlaceholder\s{0,20}\(\s{0,20}$/;
12747
13010
  POLL_SHAPE = "await expect(async () => { await page.reload(); await expect(<the finished state>).toBeVisible({ timeout: 1500 }); }).toPass({ timeout: 20000 });";
12748
13011
  }
@@ -13144,6 +13407,15 @@ function computes(line) {
13144
13407
  const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX").replace(LITERAL_ARITH_RE, "N");
13145
13408
  return COMPUTE_RE.test(code) || RELATIONAL_RE.test(line);
13146
13409
  }
13410
+ function identifiersIn(line) {
13411
+ const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX");
13412
+ return [code, ...[...line.matchAll(TEMPLATE_EXPR_RE)].map((m) => m[1])].flatMap((part) => part.match(IDENTIFIER_RE) ?? []);
13413
+ }
13414
+ function addTyped(typed, value) {
13415
+ const d = digitsOf(value);
13416
+ if (/\d/.test(d))
13417
+ typed.add(d);
13418
+ }
13147
13419
  function reasonFor4(kind, label, value) {
13148
13420
  const what = kind === "derived-value-shape-only" ? `The ${label} is checked only for its shape, so any amount passes, including a wrongly computed one.` : `The ${label} is checked against a number written into the test (${value}). That number was read off the page, so when the page computes it wrong the check still passes.`;
13149
13421
  return `${what} Instead, ${COMPUTE_SHAPE} If the number is not computed from other values (a list price, an id, a fixed fee), leave it as it is. Keep the existing checks.`;
@@ -13155,25 +13427,32 @@ function analyzeDerivedValues(rawCode) {
13155
13427
  for (const body of testBodies(rawCode)) {
13156
13428
  if (findings.length >= MAX_FINDINGS4)
13157
13429
  break;
13158
- if (body.some(computes))
13159
- continue;
13160
13430
  const typed = /* @__PURE__ */ new Set();
13161
13431
  const definitions = /* @__PURE__ */ new Map();
13432
+ const computed = /* @__PURE__ */ new Set();
13433
+ const usesComputed = (text) => identifiersIn(text).some((id) => computed.has(id));
13162
13434
  for (const line of body) {
13163
13435
  if (FILL_RE2.test(line) && !EXPECT_RE3.test(line)) {
13164
- for (const m of line.matchAll(/['"`]([^'"`\n]{0,40})['"`]/g)) {
13165
- const d = digitsOf(m[1]);
13166
- if (/\d/.test(d))
13167
- typed.add(d);
13168
- }
13436
+ for (const m of line.matchAll(QUOTED_RE))
13437
+ addTyped(typed, m[1]);
13438
+ const arg = FILL_ARG_RE.exec(line)?.[1];
13439
+ const literal = arg === void 0 ? null : LITERAL_VALUE_RE.exec(definitions.get(arg) ?? "");
13440
+ const value = literal?.slice(1).find((v) => v !== void 0);
13441
+ if (value !== void 0)
13442
+ addTyped(typed, value);
13169
13443
  }
13170
13444
  const def = DEFINITION_RE.exec(line);
13171
- if (def)
13445
+ if (def) {
13172
13446
  definitions.set(def[1], def[2]);
13447
+ if (computes(def[2]) || usesComputed(def[2]))
13448
+ computed.add(def[1]);
13449
+ }
13173
13450
  }
13174
13451
  for (const line of body) {
13175
13452
  if (!EXPECT_RE3.test(line) || !VALUE_MATCHER_RE2.test(line) || /\.not\s{0,20}\./.test(line))
13176
13453
  continue;
13454
+ if (computes(line) || usesComputed(line))
13455
+ continue;
13177
13456
  const subject = EXPECT_SUBJECT_RE.exec(line)?.[1];
13178
13457
  const label = DERIVED_WORD_RE.exec(`${line} ${(subject && definitions.get(subject)) ?? ""}`)?.[0];
13179
13458
  if (!label)
@@ -13203,7 +13482,7 @@ function formatDerivedValueAdvisory(findings) {
13203
13482
  const where = findings.length === 1 ? "One computed value is" : `${findings.length} computed values are`;
13204
13483
  return `${where} checked against a copied number or only its shape (${short4(first.line, 90)}). ${first.reason}`;
13205
13484
  }
13206
- var MAX_FINDINGS4, EXPECT_RE3, DERIVED_WORD_RE, VALUE_MATCHER_RE2, MONEY_TOKEN_RE, SHAPE_ARG_RE, FILL_RE2, EXPECT_MESSAGE_RE, STRING_RE, REGEX_LITERAL_RE, COMPUTE_RE, RELATIONAL_RE, DEFINITION_RE, EXPECT_SUBJECT_RE, LITERAL_ARITH_RE, COMPUTE_SHAPE;
13485
+ var MAX_FINDINGS4, EXPECT_RE3, DERIVED_WORD_RE, VALUE_MATCHER_RE2, MONEY_TOKEN_RE, SHAPE_ARG_RE, FILL_RE2, EXPECT_MESSAGE_RE, STRING_RE, REGEX_LITERAL_RE, COMPUTE_RE, RELATIONAL_RE, DEFINITION_RE, EXPECT_SUBJECT_RE, LITERAL_ARITH_RE, TEMPLATE_EXPR_RE, IDENTIFIER_RE, QUOTED_RE, FILL_ARG_RE, LITERAL_VALUE_RE, COMPUTE_SHAPE;
13207
13486
  var init_derived_value_check = __esm({
13208
13487
  "../shared/dist/derived-value-check.js"() {
13209
13488
  "use strict";
@@ -13223,6 +13502,11 @@ var init_derived_value_check = __esm({
13223
13502
  DEFINITION_RE = /\b(?:const|let)\s{1,10}([\w$]{1,60})\s{0,10}=\s{0,10}([^\n]{0,300})/;
13224
13503
  EXPECT_SUBJECT_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}([\w$]{1,60})/;
13225
13504
  LITERAL_ARITH_RE = /\b\d[\d_.]{0,15}(?:\s{0,5}[*/+-]\s{0,5}\d[\d_.]{0,15}){1,8}\b/g;
13505
+ TEMPLATE_EXPR_RE = /\$\{([^{}\n]{0,200})\}/g;
13506
+ IDENTIFIER_RE = /[A-Za-z_$][\w$]{0,60}/g;
13507
+ QUOTED_RE = /['"`]([^'"`\n]{0,40})['"`]/g;
13508
+ FILL_ARG_RE = /\.(?:fill|type|pressSequentially|selectOption)\s{0,20}\(\s{0,20}([A-Za-z_$][\w$]{0,60})\s{0,20}[,)]/;
13509
+ LITERAL_VALUE_RE = /^\s{0,10}(?:'([^'\n]{0,40})'|"([^"\n]{0,40})"|`([^`$\n]{0,40})`|(\d[\d_,]{0,15}(?:\.\d{1,4})?))\s{0,10};?\s{0,10}$/;
13226
13510
  COMPUTE_SHAPE = "compute the expected value in the test from its inputs (the values the test typed, or each input read from the page) and compare: const toNumber = (s) => Number(s.replace(/[^\\d.-]/g, '')); const unit = toNumber(await <unit price locator>.innerText()); then expect(toNumber(await <shown value locator>.innerText())).toBeCloseTo(unit * quantity, 2); for several lines, read them all and sum them with reduce. For a comma-decimal price like \u20AC1.234,56, drop the dots and turn the comma into a point before Number, since the replace above would read it as 1.23456.";
13227
13511
  }
13228
13512
  });
@@ -15257,6 +15541,99 @@ function formatStaleAfterSubmitAdvisory(rawCode) {
15257
15541
  const more = described.length > 3 ? ` (+${described.length - 3} more)` : "";
15258
15542
  return `stale-after-submit: ${described.slice(0, 3).join("; ")}${more}; these pass before the request lands and still pass if the server rejects or ignores the save. Wait for the result instead: page.waitForResponse around the click, or assert something the submit creates (the new row, the confirmation message, a path with an end anchor such as /\\/orders\\/\\d+$/)`;
15259
15543
  }
15544
+ function regexLiteralEnd4(text, at) {
15545
+ let inClass = false;
15546
+ for (let i = at + 1; i < text.length; i++) {
15547
+ const ch = text[i];
15548
+ if (ch === "\n")
15549
+ return -1;
15550
+ if (ch === "\\") {
15551
+ i++;
15552
+ continue;
15553
+ }
15554
+ if (inClass) {
15555
+ if (ch === "]")
15556
+ inClass = false;
15557
+ continue;
15558
+ }
15559
+ if (ch === "[") {
15560
+ inClass = true;
15561
+ continue;
15562
+ }
15563
+ if (ch === "/")
15564
+ return i + 1;
15565
+ }
15566
+ return -1;
15567
+ }
15568
+ function startsWithRegexLiteral(text) {
15569
+ const t = text.trimStart();
15570
+ return t[0] === "/" && t[1] !== "/" && t[1] !== "*" && regexLiteralEnd4(t, 0) !== -1;
15571
+ }
15572
+ function afterFirstArg(args) {
15573
+ let depth = 0;
15574
+ let quote = null;
15575
+ for (let i = 0; i < args.length; i++) {
15576
+ const ch = args[i];
15577
+ if (quote) {
15578
+ if (ch === "\\") {
15579
+ i++;
15580
+ continue;
15581
+ }
15582
+ if (ch === quote)
15583
+ quote = null;
15584
+ continue;
15585
+ }
15586
+ if (ch === '"' || ch === "'" || ch === "`") {
15587
+ quote = ch;
15588
+ continue;
15589
+ }
15590
+ if (ch === "(" || ch === "[" || ch === "{")
15591
+ depth++;
15592
+ else if (ch === ")" || ch === "]" || ch === "}")
15593
+ depth--;
15594
+ else if (ch === "," && depth === 0)
15595
+ return args.slice(i + 1);
15596
+ }
15597
+ return "";
15598
+ }
15599
+ function analyzeRegexMatchers(rawCode) {
15600
+ if (!rawCode || typeof rawCode !== "string")
15601
+ return [];
15602
+ const code = stripComments2(rawCode.slice(0, MAX_LINT_INPUT));
15603
+ const out = [];
15604
+ const re = /\bexpect(?:\.soft)?\s{0,20}\(/g;
15605
+ let m;
15606
+ let examined = 0;
15607
+ let line = 1;
15608
+ let lineAt2 = 0;
15609
+ while ((m = re.exec(code)) !== null && examined++ < MAX_CALLS && out.length < MAX_REGEX_MATCHER_FINDINGS) {
15610
+ const subj = readBalanced(code, m.index + m[0].length);
15611
+ if (!subj)
15612
+ continue;
15613
+ const mm = /^\s{0,20}(\.\s{0,20}not)?\s{0,20}\.\s{0,20}(\w{1,40})\s{0,20}\(/.exec(code.slice(subj.end + 1, subj.end + 1 + 80));
15614
+ if (!mm || !REGEX_EQUALITY_MATCHERS.has(mm[2]))
15615
+ continue;
15616
+ const args = readBalanced(code, subj.end + 1 + mm[0].length);
15617
+ if (!args)
15618
+ continue;
15619
+ const expected = mm[2] === "toHaveJSProperty" ? afterFirstArg(args.text) : args.text;
15620
+ if (!startsWithRegexLiteral(expected))
15621
+ continue;
15622
+ if (mm[2] !== "toHaveJSProperty" && mm[2] !== "toContain" && (startsWithRegexLiteral(subj.text) || /^new\s+RegExp\b/.test(subj.text.trim())))
15623
+ continue;
15624
+ for (let i = code.indexOf("\n", lineAt2); i !== -1 && i < m.index; i = code.indexOf("\n", i + 1)) {
15625
+ line++;
15626
+ lineAt2 = i + 1;
15627
+ }
15628
+ out.push({ line, matcher: mm[2], negated: mm[1] !== void 0 });
15629
+ }
15630
+ return out;
15631
+ }
15632
+ function formatRegexMatcherNote(findings) {
15633
+ const lines = findings.map((f) => `Line ${f.line}: .${f.negated ? "not." : ""}${f.matcher}(\u2026 /regex/ \u2026) ${f.negated ? "always passes (a regex never equals the value), so it checks nothing" : "can never pass: this matcher compares by equality and a regex never equals a value"}.`);
15634
+ return `${lines.join("\n")}
15635
+ A regex only works in toMatch, toHaveText, toContainText, toHaveValue and similar text matchers. For a DOM property read it first: expect(await loc.evaluate((n) => n.<prop>)).toMatch(/re/). Or give toHaveJSProperty the exact string (or boolean) the page shows. Keep every other line as-is.`;
15636
+ }
15260
15637
  function lintTestQuality(rawCode, opts = {}) {
15261
15638
  if (!rawCode || typeof rawCode !== "string")
15262
15639
  return [];
@@ -15264,6 +15641,10 @@ function lintTestQuality(rawCode, opts = {}) {
15264
15641
  return lintTestData(rawCode, { pageContacts: opts.pageContacts });
15265
15642
  if (opts.only === "test-data-blocking")
15266
15643
  return analyzeTestData(rawCode, { pageContacts: opts.pageContacts }).blocking;
15644
+ if (opts.only === "regex-matcher") {
15645
+ const found = analyzeRegexMatchers(rawCode);
15646
+ return found.length > 0 ? [formatRegexMatcherNote(found)] : [];
15647
+ }
15267
15648
  const warnings = [];
15268
15649
  if (rawCode.length > MAX_LINT_INPUT) {
15269
15650
  warnings.push(`The code is ${Math.round(rawCode.length / 1024)} KB; the quality lint checked only the first ${MAX_LINT_INPUT / 1e3} KB. Split it into smaller tests (one behaviour per test).`);
@@ -15538,7 +15919,7 @@ function specItemsFromTestPlans(testPlans) {
15538
15919
  }
15539
15920
  return [...new Set(out)].slice(0, 200);
15540
15921
  }
15541
- var READ_ONLY_TAGS, PRODUCTION_READ_ONLY_GUARD_TEMPLATE, HOST_AGNOSTIC_URL_TEMPLATE, TEST_QUALITY_CONVENTIONS, NON_PROD_HOST_LABEL, NON_PROD_HOST_TOKEN, TRANSIENT_TEXT, STRING_LITERAL, FIRST_LITERAL, GET_BY_TEXT_LITERAL2, ROLE_NAME_LITERAL2, MAX_LINT_INPUT, MAX_BALANCED_SCAN, MAX_CALLS, MAX_SEEN_SCAN, ACTION_CALL, BY_NAME_LOCATOR, ACTION_AFTER, MAX_CONTACTS, MAX_SNAPSHOT_SCAN, PHONE_IN_TEXT, EMAIL_IN_TEXT, TEL_HREF, MAILTO_HREF, FIELD_LINE, INBOX_EMAIL_READ, MAIL_READ_CALL, RESERVED_EMAIL_TLD, PHONE_LABEL, NON_PHONE_NUMBER_LABEL, IDENTITY_LABEL, LOOKUP_LABEL, NON_NAME_SUFFIX_LABEL, DATE_LIKE, SIGNUP_ACTION_NAME, SIGNUP_SUBMIT_NAME, LOGIN_ACTION_NAME, SIGNUP_GOTO, LOGIN_GOTO, MINTED_INBOX_ALIAS, STAMP_EVAL, RECORDING_VALUE_PLACEHOLDERS, NEGATIVE_TEST_TITLE, NEGATIVE_SIGNUP_ASSERT, STORED_LOGIN_RE, CONFIRM_CLICK_RE, SAVE_UPDATE_RE, NOT_A_CONFIRM_RE, ENTITY_WORDS, CREATE_ENTITY_CLICK_RE, RESTORE_CLICK_RE, MAX_ACCOUNT_ACTIONS, MAX_TRY_BLOCKS, MAX_STATEMENT_LOOKBACK, MAX_FINDINGS5, MAX_TRY_BODY, AUTH_REGION_START, AUTH_REGION_END, INBOX_REGION_START, INBOX_REGION_END, ACCOUNT_ROUTE_RE, ENTITY_WORD_RES, PERSIST_CALL_RE, FILL_IDENT_ARG_RE, PERSIST_ARGS_RE, HOST_IN_URL_ARG, URL_WAIT_TOKEN, GLOB_ESCAPED, URL_WAIT_MAX_PATH, URL_WAIT_MAX_GLOB_STARS, UNREACHED_MAX_PATHS, UNREACHED_MATCH_BUDGET, STALE_TOKEN, RESULT_WAIT, WRITE_CALL_PRESENT, SUBMIT_CALL_PRESENT, TEST_CALL, VALUE_WRITE_CALLS, SUBMIT_CALLS, DISTINCT_PAGE_CALLS, OWN_CONTROL_MATCHERS, OUTCOME_CONTENT_MATCHERS, OUTCOME_STATE_MATCHERS, ANY_CONTENT_ARG, ROOT_SUBJECT, LITERAL_SUBJECT, MATCHER_AFTER_EXPECT, STALE_MAX_EVENTS, CHECKED, UNCHECKED, squash, NOT_JUDGED, STALE_REASON_TEXT, LIVE_WRITE_NAME, BUNDLED_CHECKS_BEFORE_WRITE, STOP, SYNONYMS, AREA_TOKENS;
15922
+ var READ_ONLY_TAGS, PRODUCTION_READ_ONLY_GUARD_TEMPLATE, HOST_AGNOSTIC_URL_TEMPLATE, TEST_QUALITY_CONVENTIONS, NON_PROD_HOST_LABEL, NON_PROD_HOST_TOKEN, TRANSIENT_TEXT, STRING_LITERAL, FIRST_LITERAL, GET_BY_TEXT_LITERAL2, ROLE_NAME_LITERAL2, MAX_LINT_INPUT, MAX_BALANCED_SCAN, MAX_CALLS, MAX_SEEN_SCAN, ACTION_CALL, BY_NAME_LOCATOR, ACTION_AFTER, MAX_CONTACTS, MAX_SNAPSHOT_SCAN, PHONE_IN_TEXT, EMAIL_IN_TEXT, TEL_HREF, MAILTO_HREF, FIELD_LINE, INBOX_EMAIL_READ, MAIL_READ_CALL, RESERVED_EMAIL_TLD, PHONE_LABEL, NON_PHONE_NUMBER_LABEL, IDENTITY_LABEL, LOOKUP_LABEL, NON_NAME_SUFFIX_LABEL, DATE_LIKE, SIGNUP_ACTION_NAME, SIGNUP_SUBMIT_NAME, LOGIN_ACTION_NAME, SIGNUP_GOTO, LOGIN_GOTO, MINTED_INBOX_ALIAS, STAMP_EVAL, RECORDING_VALUE_PLACEHOLDERS, NEGATIVE_TEST_TITLE, NEGATIVE_SIGNUP_ASSERT, STORED_LOGIN_RE, CONFIRM_CLICK_RE, SAVE_UPDATE_RE, NOT_A_CONFIRM_RE, ENTITY_WORDS, CREATE_ENTITY_CLICK_RE, RESTORE_CLICK_RE, MAX_ACCOUNT_ACTIONS, MAX_TRY_BLOCKS, MAX_STATEMENT_LOOKBACK, MAX_FINDINGS5, MAX_TRY_BODY, AUTH_REGION_START, AUTH_REGION_END, INBOX_REGION_START, INBOX_REGION_END, ACCOUNT_ROUTE_RE, ENTITY_WORD_RES, PERSIST_CALL_RE, FILL_IDENT_ARG_RE, PERSIST_ARGS_RE, HOST_IN_URL_ARG, URL_WAIT_TOKEN, GLOB_ESCAPED, URL_WAIT_MAX_PATH, URL_WAIT_MAX_GLOB_STARS, UNREACHED_MAX_PATHS, UNREACHED_MATCH_BUDGET, STALE_TOKEN, RESULT_WAIT, WRITE_CALL_PRESENT, SUBMIT_CALL_PRESENT, TEST_CALL, VALUE_WRITE_CALLS, SUBMIT_CALLS, DISTINCT_PAGE_CALLS, OWN_CONTROL_MATCHERS, OUTCOME_CONTENT_MATCHERS, OUTCOME_STATE_MATCHERS, ANY_CONTENT_ARG, ROOT_SUBJECT, LITERAL_SUBJECT, MATCHER_AFTER_EXPECT, STALE_MAX_EVENTS, CHECKED, UNCHECKED, squash, NOT_JUDGED, STALE_REASON_TEXT, LIVE_WRITE_NAME, BUNDLED_CHECKS_BEFORE_WRITE, MAX_REGEX_MATCHER_FINDINGS, REGEX_EQUALITY_MATCHERS, STOP, SYNONYMS, AREA_TOKENS;
15542
15923
  var init_test_authoring_quality = __esm({
15543
15924
  "../shared/dist/test-authoring-quality.js"() {
15544
15925
  "use strict";
@@ -15770,6 +16151,8 @@ var init_test_authoring_quality = __esm({
15770
16151
  };
15771
16152
  LIVE_WRITE_NAME = /\b(?:submit|place|book|reserve|confirm|pay|send|checkout|order|register|sign\s?up|create account)\b/i;
15772
16153
  BUNDLED_CHECKS_BEFORE_WRITE = 5;
16154
+ MAX_REGEX_MATCHER_FINDINGS = 10;
16155
+ REGEX_EQUALITY_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual", "toContain", "toHaveJSProperty"]);
15773
16156
  STOP = /* @__PURE__ */ new Set([
15774
16157
  "a",
15775
16158
  "an",
@@ -15998,6 +16381,19 @@ var init_fence_runs = __esm({
15998
16381
  }
15999
16382
  });
16000
16383
 
16384
+ // ../shared/dist/trailing-slashes.js
16385
+ function stripTrailingSlashes(value) {
16386
+ let end = value.length;
16387
+ while (end > 0 && value.charCodeAt(end - 1) === 47)
16388
+ end--;
16389
+ return end === value.length ? value : value.slice(0, end);
16390
+ }
16391
+ var init_trailing_slashes = __esm({
16392
+ "../shared/dist/trailing-slashes.js"() {
16393
+ "use strict";
16394
+ }
16395
+ });
16396
+
16001
16397
  // ../shared/dist/survey-bug-signature.js
16002
16398
  function normalizeWords(text) {
16003
16399
  return text.toLowerCase().replace(UUID, " id ").replace(PREFIXED_ID, " id ").replace(HEX_RUN, (run) => /\d/.test(run) ? " id " : run).replace(NUMBER_RUN, " # ").replace(/[^\p{L}\p{N}#]+/gu, " ").replace(/\s+/g, " ").trim();
@@ -16008,7 +16404,7 @@ function normalizeBugPath(route) {
16008
16404
  if (hash >= 0 && path[hash + 1] !== "/")
16009
16405
  path = path.slice(0, hash);
16010
16406
  const segments = path.toLowerCase().split("/").map((segment) => /\d/.test(segment) ? ":id" : segment);
16011
- const joined = segments.join("/").replace(/\/+$/, "");
16407
+ const joined = stripTrailingSlashes(segments.join("/"));
16012
16408
  return joined || (path.startsWith("/") ? "/" : "");
16013
16409
  }
16014
16410
  function isGenericConsoleMessage(message) {
@@ -16142,6 +16538,7 @@ var SIGNATURE_SOURCE_MAX_CHARS, UUID, PREFIXED_ID, HEX_RUN, NUMBER_RUN, GENERIC_
16142
16538
  var init_survey_bug_signature = __esm({
16143
16539
  "../shared/dist/survey-bug-signature.js"() {
16144
16540
  "use strict";
16541
+ init_trailing_slashes();
16145
16542
  SIGNATURE_SOURCE_MAX_CHARS = 800;
16146
16543
  UUID = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g;
16147
16544
  PREFIXED_ID = /\b[a-z]{1,8}[-_]\d+\b/g;
@@ -17422,9 +17819,9 @@ function lastLocatorLabels(chain) {
17422
17819
  if (t)
17423
17820
  labels.push(t[1]);
17424
17821
  }
17425
- const has = optionText(last.args, HAS_TEXT_OPTION_RE);
17426
- if (has !== null)
17427
- labels.push(has);
17822
+ const has2 = optionText(last.args, HAS_TEXT_OPTION_RE);
17823
+ if (has2 !== null)
17824
+ labels.push(has2);
17428
17825
  }
17429
17826
  return labels.map((l) => l.replace(/^[\s^(?:]+/, "").trim());
17430
17827
  }
@@ -18181,14 +18578,17 @@ var init_promotion_quality_gate = __esm({
18181
18578
  SOURCE_RANK = { generator: 0, classifier: 0, healer: 1, human: 2 };
18182
18579
  strengthCheck = {
18183
18580
  id: "assertion-strength",
18184
- run({ code, strengthGate, flagContradictedAdditions }) {
18581
+ run({ code, tags, strengthGate, flagContradictedAdditions }) {
18185
18582
  const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true });
18186
18583
  if (verdict.severity === "route-bug")
18187
18584
  return [{ check: "route-bug" }];
18188
- if (verdict.severity !== "block")
18585
+ const authDowngrade = verdict.severity === "downgrade" && tags.some((t) => t.toLowerCase() === "@auth");
18586
+ if (verdict.severity !== "block" && !authDowngrade)
18189
18587
  return [];
18190
18588
  const revealed = strengthGate?.severity === "none" && strengthGate.revealEvidence === "revealed" && strengthGate.source !== "heal";
18191
- return revealed ? [] : [{ check: "strength-block" }];
18589
+ if (revealed)
18590
+ return [];
18591
+ return [{ check: authDowngrade ? "strength-downgrade-auth" : "strength-block" }];
18192
18592
  }
18193
18593
  };
18194
18594
  STRING_CONST = /\b(?:const|let|var)\s{1,20}([A-Za-z_$][\w$]{0,60})\s{0,20}=\s{0,20}(?:'([^'\\\n]{0,300})'|"([^"\\\n]{0,300})"|`([^`\\$\n]{0,300})`)\s{0,20}[;\n]/g;
@@ -18595,6 +18995,7 @@ __export(dist_exports, {
18595
18995
  AUDIO_CHUNK_DURATION_MS: () => AUDIO_CHUNK_DURATION_MS,
18596
18996
  AUTH_PATH_SIGNATURE: () => AUTH_PATH_SIGNATURE,
18597
18997
  AUTH_PROVIDER_HOST_SIGNATURES: () => AUTH_PROVIDER_HOST_SIGNATURES,
18998
+ AUTH_SESSION_GENERATION_RE: () => AUTH_SESSION_GENERATION_RE,
18598
18999
  AUTH_STATE_EXPIRY_SKEW_MS: () => AUTH_STATE_EXPIRY_SKEW_MS,
18599
19000
  AUTH_TAG_COOKIE_STATE: () => AUTH_TAG_COOKIE_STATE,
18600
19001
  AUTH_TAG_LOGIN_BEARER: () => AUTH_TAG_LOGIN_BEARER,
@@ -18727,6 +19128,8 @@ __export(dist_exports, {
18727
19128
  LIVE_FRAME_STALE_MS: () => LIVE_FRAME_STALE_MS,
18728
19129
  LIVE_FRAME_UNFOCUSED_INTERVAL_MS: () => LIVE_FRAME_UNFOCUSED_INTERVAL_MS,
18729
19130
  LIVE_THUMBNAIL_MAX_BASE64_BYTES: () => LIVE_THUMBNAIL_MAX_BASE64_BYTES,
19131
+ MAX_BALANCED_SCAN: () => MAX_BALANCED_SCAN,
19132
+ MAX_CALLS: () => MAX_CALLS,
18730
19133
  MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
18731
19134
  MAX_DEEPLINK_LENGTH: () => MAX_DEEPLINK_LENGTH,
18732
19135
  MAX_EMAIL_ADDRESS_LENGTH: () => MAX_EMAIL_ADDRESS_LENGTH,
@@ -18771,6 +19174,7 @@ __export(dist_exports, {
18771
19174
  MUSE_ENGINE_DEFAULT_MODEL: () => MUSE_ENGINE_DEFAULT_MODEL,
18772
19175
  MUSE_ENGINE_MODELS: () => MUSE_ENGINE_MODELS,
18773
19176
  NOREPLY_EMAIL: () => NOREPLY_EMAIL,
19177
+ OPENCODE_GO_2_PREFIX: () => OPENCODE_GO_2_PREFIX,
18774
19178
  OPENCODE_GO_MODEL_PATTERN: () => OPENCODE_GO_MODEL_PATTERN,
18775
19179
  OPENCODE_GO_PREFIX: () => OPENCODE_GO_PREFIX,
18776
19180
  OPENROUTER_MAX_CALL_COST_USD: () => OPENROUTER_MAX_CALL_COST_USD,
@@ -18806,6 +19210,8 @@ __export(dist_exports, {
18806
19210
  PROMOTION_GATE_MAX_CODE_CHARS: () => PROMOTION_GATE_MAX_CODE_CHARS,
18807
19211
  PROMOTION_QUALITY_CHECKS: () => PROMOTION_QUALITY_CHECKS,
18808
19212
  PROVIDER_CHAINS: () => PROVIDER_CHAINS,
19213
+ PROVIDER_CHAIN_LEADS: () => PROVIDER_CHAIN_LEADS,
19214
+ PROVIDER_HOST_NOT_OFFERED: () => PROVIDER_HOST_NOT_OFFERED,
18809
19215
  PUBLIC_PLANS: () => PUBLIC_PLANS,
18810
19216
  QUARANTINE_CLOSE_TAGS: () => QUARANTINE_CLOSE_TAGS,
18811
19217
  READINESS_POLL_MAX_CODE_CHARS: () => READINESS_POLL_MAX_CODE_CHARS,
@@ -18844,6 +19250,7 @@ __export(dist_exports, {
18844
19250
  SURVEY_NOTES_RENDER_MAX_CHARS: () => SURVEY_NOTES_RENDER_MAX_CHARS,
18845
19251
  SURVEY_NOTE_KEYS: () => SURVEY_NOTE_KEYS,
18846
19252
  SUSPICIOUS_PASS_TAG: () => SUSPICIOUS_PASS_TAG,
19253
+ TEST_DID_NOT_RUN_ERROR: () => TEST_DID_NOT_RUN_ERROR,
18847
19254
  TEST_QUALITY_CONVENTIONS: () => TEST_QUALITY_CONVENTIONS,
18848
19255
  TEST_USER_ENV_KEYS: () => TEST_USER_ENV_KEYS,
18849
19256
  TEST_VARIABLE_SECRET_KEY_PATTERN: () => TEST_VARIABLE_SECRET_KEY_PATTERN,
@@ -18859,6 +19266,8 @@ __export(dist_exports, {
18859
19266
  VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
18860
19267
  WEB_PROXY_CHAIN_HEADER: () => WEB_PROXY_CHAIN_HEADER,
18861
19268
  WITHHELD_CREDENTIAL_TEXT: () => WITHHELD_CREDENTIAL_TEXT,
19269
+ ZAI_MODEL_PATTERN: () => ZAI_MODEL_PATTERN,
19270
+ ZAI_PREFIX: () => ZAI_PREFIX,
18862
19271
  accumulateStatements: () => accumulateStatements,
18863
19272
  adoptBrokerFailover: () => adoptBrokerFailover,
18864
19273
  aiCostToCreditCents: () => aiCostToCreditCents,
@@ -18867,6 +19276,7 @@ __export(dist_exports, {
18867
19276
  analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
18868
19277
  analyzePersistenceReread: () => analyzePersistenceReread,
18869
19278
  analyzeReadinessPolling: () => analyzeReadinessPolling,
19279
+ analyzeRegexMatchers: () => analyzeRegexMatchers,
18870
19280
  analyzeSharedAccountUse: () => analyzeSharedAccountUse,
18871
19281
  analyzeStaleAfterSubmit: () => analyzeStaleAfterSubmit,
18872
19282
  analyzeTestData: () => analyzeTestData,
@@ -19030,6 +19440,7 @@ __export(dist_exports, {
19030
19440
  formatPersistenceRereadAdvisory: () => formatPersistenceRereadAdvisory,
19031
19441
  formatPoolBudgetLine: () => formatPoolBudgetLine,
19032
19442
  formatReadinessPollAdvisory: () => formatReadinessPollAdvisory,
19443
+ formatRegexMatcherNote: () => formatRegexMatcherNote,
19033
19444
  formatStaleAfterSubmitAdvisory: () => formatStaleAfterSubmitAdvisory,
19034
19445
  formatSuspiciousExpectation: () => formatSuspiciousExpectation,
19035
19446
  formatUrlWaitAdvisory: () => formatUrlWaitAdvisory,
@@ -19068,12 +19479,14 @@ __export(dist_exports, {
19068
19479
  hasEncodedRequestBody: () => hasEncodedRequestBody,
19069
19480
  hasEngineSpecParts: () => hasEngineSpecParts,
19070
19481
  hasOnlyConstantAssertions: () => hasOnlyConstantAssertions,
19482
+ hasOpenCodeGo2Prefix: () => hasOpenCodeGo2Prefix,
19071
19483
  hasOpenCodeGoPrefix: () => hasOpenCodeGoPrefix,
19072
19484
  hasOpenRouterPrefix: () => hasOpenRouterPrefix,
19073
19485
  hasPlatformLocator: () => hasPlatformLocator,
19074
19486
  hasReadOnlyMarker: () => hasReadOnlyMarker,
19075
19487
  hasSurveyNotes: () => hasSurveyNotes,
19076
19488
  hasTestFailContract: () => hasTestFailContract,
19489
+ hasZaiPrefix: () => hasZaiPrefix,
19077
19490
  healMobileTestSteps: () => healMobileTestSteps,
19078
19491
  healthRunWhere: () => healthRunWhere,
19079
19492
  heldCodeRecord: () => heldCodeRecord,
@@ -19177,6 +19590,7 @@ __export(dist_exports, {
19177
19590
  isValidSttProvider: () => isValidSttProvider,
19178
19591
  isValidTtsModel: () => isValidTtsModel,
19179
19592
  isValidTtsVoiceId: () => isValidTtsVoiceId,
19593
+ isZaiModelId: () => isZaiModelId,
19180
19594
  jsEscapedForms: () => jsEscapedForms,
19181
19595
  jsonStructureRefusalOf: () => jsonStructureRefusalOf,
19182
19596
  jsonStructureVerify: () => jsonStructureVerify,
@@ -19225,6 +19639,7 @@ __export(dist_exports, {
19225
19639
  parseApiCardModelUpdate: () => parseApiCardModelUpdate,
19226
19640
  parseAppTimezone: () => parseAppTimezone,
19227
19641
  parseAuditGroupScopeId: () => parseAuditGroupScopeId,
19642
+ parseAuthSessionStamp: () => parseAuthSessionStamp,
19228
19643
  parseBoolFlag: () => parseBoolFlag,
19229
19644
  parseDbPoolSize: () => parseDbPoolSize,
19230
19645
  parseDeepgramListenResponse: () => parseDeepgramListenResponse,
@@ -19268,7 +19683,11 @@ __export(dist_exports, {
19268
19683
  profileKeyRoute: () => profileKeyRoute,
19269
19684
  projectAppHosts: () => projectAppHosts,
19270
19685
  providerChainFor: () => providerChainFor,
19686
+ providerChainHostOf: () => providerChainHostOf,
19687
+ providerChainLeadsFor: () => providerChainLeadsFor,
19271
19688
  providerChainPrimariesForHop: () => providerChainPrimariesForHop,
19689
+ providerHostPlan: () => providerHostPlan,
19690
+ readBalanced: () => readBalanced,
19272
19691
  readChatFailover: () => readChatFailover,
19273
19692
  readChatReasoningTokens: () => readChatReasoningTokens,
19274
19693
  readChatUsage: () => readChatUsage,
@@ -19351,6 +19770,7 @@ __export(dist_exports, {
19351
19770
  stripCoverageComments: () => stripCoverageComments,
19352
19771
  stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
19353
19772
  stripLoginScaffolding: () => stripLoginScaffolding,
19773
+ stripTrailingSlashes: () => stripTrailingSlashes,
19354
19774
  strongerNeedsReviewSource: () => strongerNeedsReviewSource,
19355
19775
  sttModelsForProvider: () => sttModelsForProvider,
19356
19776
  summarizeHealth: () => summarizeHealth,
@@ -19378,7 +19798,8 @@ __export(dist_exports, {
19378
19798
  withReviewSource: () => withReviewSource,
19379
19799
  withoutNotChargedClaim: () => withoutNotChargedClaim,
19380
19800
  withoutServerOnlySpendFields: () => withoutServerOnlySpendFields,
19381
- withoutSurveyNotes: () => withoutSurveyNotes
19801
+ withoutSurveyNotes: () => withoutSurveyNotes,
19802
+ zaiWireModel: () => zaiWireModel
19382
19803
  });
19383
19804
  var init_dist = __esm({
19384
19805
  "../shared/dist/index.js"() {
@@ -19420,6 +19841,7 @@ var init_dist = __esm({
19420
19841
  init_admin_view_return_to();
19421
19842
  init_auth_plan();
19422
19843
  init_auth_block_detector();
19844
+ init_auth_session_stamp();
19423
19845
  init_auth_state_freshness();
19424
19846
  init_skip_coverage_detector();
19425
19847
  init_feature_coverage();
@@ -19476,6 +19898,7 @@ var init_dist = __esm({
19476
19898
  init_promotion_quality_gate();
19477
19899
  init_regex_alternation();
19478
19900
  init_page_evidence();
19901
+ init_trailing_slashes();
19479
19902
  }
19480
19903
  });
19481
19904
 
@@ -19492,8 +19915,11 @@ var require_survey_network_guard = __commonJS({
19492
19915
  exports2.maskedPath = maskedPath;
19493
19916
  exports2.bodyKeysOf = bodyKeysOf;
19494
19917
  exports2.graphqlDocumentIsQueryOnly = graphqlDocumentIsQueryOnly;
19918
+ exports2.graphqlOperationRoots = graphqlOperationRoots;
19495
19919
  exports2.isTokenRefreshWrite = isTokenRefreshWrite;
19496
19920
  exports2.isSignInPathWrite = isSignInPathWrite;
19921
+ exports2.graphqlUrlCarriesQuery = graphqlUrlCarriesQuery;
19922
+ exports2.graphqlGetMayWrite = graphqlGetMayWrite;
19497
19923
  exports2.writeShapedGet = writeShapedGet;
19498
19924
  exports2.decideSurveyRequest = decideSurveyRequest;
19499
19925
  exports2.installSurveyNetworkGuard = installSurveyNetworkGuard;
@@ -19770,6 +20196,100 @@ var require_survey_network_guard = __commonJS({
19770
20196
  }
19771
20197
  return depth === 0 && parens === 0 && objects === 0 && sawOperation;
19772
20198
  }
20199
+ function graphqlOperationRoots(text) {
20200
+ const tokens2 = lexGraphql(text);
20201
+ if (!tokens2)
20202
+ return null;
20203
+ const ops = [];
20204
+ let depth = 0;
20205
+ let parens = 0;
20206
+ let objects = 0;
20207
+ let defStart = true;
20208
+ let kind = null;
20209
+ let current = null;
20210
+ let prev = "";
20211
+ for (const tok of tokens2) {
20212
+ const last = prev;
20213
+ prev = tok;
20214
+ if (tok === "(") {
20215
+ parens++;
20216
+ continue;
20217
+ }
20218
+ if (tok === ")") {
20219
+ parens--;
20220
+ if (parens < 0 || objects !== 0)
20221
+ return null;
20222
+ continue;
20223
+ }
20224
+ if (parens > 0) {
20225
+ if (tok === "{")
20226
+ objects++;
20227
+ else if (tok === "}") {
20228
+ objects--;
20229
+ if (objects < 0)
20230
+ return null;
20231
+ }
20232
+ continue;
20233
+ }
20234
+ if (tok === "{") {
20235
+ if (depth === 0) {
20236
+ if (defStart)
20237
+ kind = "query";
20238
+ else if (kind === null)
20239
+ return null;
20240
+ current = kind === "fragment" ? null : { kind, roots: [] };
20241
+ if (current)
20242
+ ops.push(current);
20243
+ defStart = false;
20244
+ }
20245
+ depth++;
20246
+ continue;
20247
+ }
20248
+ if (tok === "}") {
20249
+ depth--;
20250
+ if (depth < 0)
20251
+ return null;
20252
+ if (depth === 0) {
20253
+ defStart = true;
20254
+ kind = null;
20255
+ current = null;
20256
+ }
20257
+ continue;
20258
+ }
20259
+ if (depth === 0) {
20260
+ if (!/^[_A-Za-z]/.test(tok)) {
20261
+ if (tok === "@")
20262
+ continue;
20263
+ return null;
20264
+ }
20265
+ if (defStart) {
20266
+ if (tok !== "query" && tok !== "mutation" && tok !== "fragment")
20267
+ return null;
20268
+ kind = tok;
20269
+ defStart = false;
20270
+ }
20271
+ continue;
20272
+ }
20273
+ if (depth > 1 || !current)
20274
+ continue;
20275
+ if (/^[_A-Za-z]/.test(tok)) {
20276
+ if (last === "@")
20277
+ continue;
20278
+ if (last === ":") {
20279
+ if (current.roots.length === 0)
20280
+ return null;
20281
+ current.roots[current.roots.length - 1] = tok;
20282
+ continue;
20283
+ }
20284
+ current.roots.push(tok);
20285
+ continue;
20286
+ }
20287
+ if (tok === ":" || tok === "@")
20288
+ continue;
20289
+ return null;
20290
+ }
20291
+ return depth === 0 && parens === 0 && objects === 0 ? ops : null;
20292
+ }
19773
20293
  function isGraphqlQueryOnly(postData) {
19774
20294
  if (!postData || postData.length > MAX_BODY_CHARS)
19775
20295
  return false;
@@ -19807,6 +20327,34 @@ var require_survey_network_guard = __commonJS({
19807
20327
  "remove"
19808
20328
  ]);
19809
20329
  var NOT_A_SESSION_END = (segs) => !segs.some((seg) => SESSION_END_SEGMENTS.has(seg));
20330
+ var DESTRUCTIVE_WORDS = /* @__PURE__ */ new Set([
20331
+ "delete",
20332
+ "remove",
20333
+ "destroy",
20334
+ "deactivate",
20335
+ "terminate",
20336
+ "purge",
20337
+ "wipe",
20338
+ "erase",
20339
+ "cancel",
20340
+ "close",
20341
+ "revoke",
20342
+ "unlink",
20343
+ "disconnect"
20344
+ ]);
20345
+ var DESTRUCTIVE_PREFIXES = ["delete", "remove", "destroy", "deactivate", "terminate", "purge"];
20346
+ function hasDestructiveSegment(rawSegs) {
20347
+ return rawSegs.some((raw) => {
20348
+ let seg = raw;
20349
+ try {
20350
+ seg = decodeURIComponent(raw);
20351
+ } catch {
20352
+ }
20353
+ const words = seg.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/);
20354
+ return words.some((word) => DESTRUCTIVE_WORDS.has(word) || DESTRUCTIVE_PREFIXES.some((p) => word.startsWith(p)));
20355
+ });
20356
+ }
20357
+ var rawPathSegments = (pathname) => pathname.split("/").map((seg) => seg.split(";")[0]).filter(Boolean);
19810
20358
  var pathSegments = (pathname) => pathname.toLowerCase().split("/").map((seg) => seg.split(";")[0]).filter(Boolean);
19811
20359
  function isTokenRefreshWrite(method, pathname, recentAction = false) {
19812
20360
  if (method.toUpperCase() !== "POST")
@@ -19823,6 +20371,8 @@ var require_survey_network_guard = __commonJS({
19823
20371
  var AUTH_PAGE_PATH_RE = /(?:^|\/)(?:log-?in|sign-?in|sign_in|auth|oauth2?|sso|callback)(?:\/|$)/;
19824
20372
  var SIGN_IN_SEGMENT_RE = /^(?:log-?in|sign-?in|sign_in)$/;
19825
20373
  var CALLBACK_ROOT_SEGMENTS = /* @__PURE__ */ new Set(["auth", "oauth", "oauth2", "sso"]);
20374
+ var CALLBACK_PROTOCOL_SEGMENTS = /* @__PURE__ */ new Set(["oidc", "openid", "openid-connect", "openidconnect", "saml", "saml2", "oauth", "oauth2"]);
20375
+ var API_VERSION_SEGMENT_RE = /^v[0-9]{1,3}$/;
19826
20376
  var PROVIDER_SEGMENT_RE = /^[a-z][a-z0-9_-]{0,39}$/;
19827
20377
  var NOT_A_PROVIDER_SEGMENTS = /* @__PURE__ */ new Set([
19828
20378
  "link",
@@ -19861,14 +20411,21 @@ var require_survey_network_guard = __commonJS({
19861
20411
  if (!NOT_A_SESSION_END(segs))
19862
20412
  return false;
19863
20413
  if (AUTH_END_PATH_RE.test(`/${segs.join("/")}`))
19864
- return true;
20414
+ return !hasDestructiveSegment(rawPathSegments(pathname).slice(0, -1));
20415
+ let top = segs[0] === "api" ? 1 : 0;
20416
+ if (top < segs.length && API_VERSION_SEGMENT_RE.test(segs[top]))
20417
+ top += 1;
20418
+ const below = segs.length - top;
20419
+ const isProvider = (seg) => PROVIDER_SEGMENT_RE.test(seg) && !NOT_A_PROVIDER_SEGMENTS.has(seg);
19865
20420
  const provider = segs[segs.length - 1];
19866
- const authRootAtTop = segs.length === 3 || segs[segs.length - 4] === "api" && (segs.length === 4 || segs[segs.length - 3] === "auth");
19867
- if (segs.length >= 3 && authRootAtTop && CALLBACK_ROOT_SEGMENTS.has(segs[segs.length - 3]) && PROVIDER_SEGMENT_RE.test(provider) && !NOT_A_PROVIDER_SEGMENTS.has(provider)) {
20421
+ const authRootAtTop = below === 3 || segs[segs.length - 4] === "api" && segs[segs.length - 3] === "auth";
20422
+ if (segs.length >= 3 && authRootAtTop && CALLBACK_ROOT_SEGMENTS.has(segs[segs.length - 3]) && isProvider(provider)) {
19868
20423
  const step = segs[segs.length - 2];
19869
20424
  if (step === "callback" || step === "signin")
19870
20425
  return true;
19871
20426
  }
20427
+ if (below === 4 && CALLBACK_ROOT_SEGMENTS.has(segs[top]) && CALLBACK_PROTOCOL_SEGMENTS.has(segs[top + 1]) && isProvider(segs[top + 2]) && segs[top + 3] === "callback")
20428
+ return true;
19872
20429
  if (segs.length < 2 || segs[segs.length - 1] !== "callback")
19873
20430
  return false;
19874
20431
  const parent = segs[segs.length - 2];
@@ -19895,7 +20452,7 @@ var require_survey_network_guard = __commonJS({
19895
20452
  try {
19896
20453
  const a = new URL(url);
19897
20454
  const b = new URL(appBaseUrl);
19898
- const path = (u) => u.pathname.replace(/\/+$/, "");
20455
+ const path = (u) => (0, shared_1.stripTrailingSlashes)(u.pathname);
19899
20456
  return a.origin === b.origin && path(a) === path(b);
19900
20457
  } catch {
19901
20458
  return false;
@@ -19917,9 +20474,15 @@ var require_survey_network_guard = __commonJS({
19917
20474
  const bodyHash = (0, node_crypto_1.createHash)("sha256").update(req.postData ?? "").digest("hex").slice(0, 16);
19918
20475
  return `${record.method} ${url.hostname.toLowerCase()}${url.pathname} ${bodyHash}`;
19919
20476
  }
19920
- function isGraphqlGetWrite(url, s) {
19921
- const doc = url.searchParams.get("query");
19922
- return doc !== null && isGraphqlEndpoint(url, s) && !graphqlDocumentIsQueryOnly(doc);
20477
+ function graphqlUrlCarriesQuery(params) {
20478
+ return params.has("query");
20479
+ }
20480
+ function graphqlGetMayWrite(url, body) {
20481
+ const docs = url.searchParams.getAll("query");
20482
+ return docs.length > 0 && (typeof body === "string" && body.length > 0 || docs.some((doc) => !graphqlDocumentIsQueryOnly(doc)));
20483
+ }
20484
+ function isGraphqlGetWrite(url, s, body) {
20485
+ return isGraphqlEndpoint(url, s) && graphqlGetMayWrite(url, body);
19923
20486
  }
19924
20487
  exports2.WRITE_VERBS = /* @__PURE__ */ new Set([
19925
20488
  "cancel",
@@ -20123,7 +20686,7 @@ var require_survey_network_guard = __commonJS({
20123
20686
  if (url.protocol !== "http:" && url.protocol !== "https:")
20124
20687
  return { action: "allow", rule: readMethod ? "read" : "non-http" };
20125
20688
  const appSite = isAppSite(url.hostname, s.appBaseUrl);
20126
- const graphqlGet = method === "GET" && isGraphqlGetWrite(url, s);
20689
+ const graphqlGet = method === "GET" && isGraphqlGetWrite(url, s, req.postData);
20127
20690
  const inActionWindow = s.lastActionAt > 0 && s.now - s.lastActionAt <= ACTION_NAVIGATION_WINDOW_MS;
20128
20691
  const typedLoad = s.agentLoad === true && req.resourceType === "document";
20129
20692
  const writeShaped = method === "GET" && appSite && !graphqlGet && (inActionWindow || typedLoad) ? writeShapedGet(req.url, {
@@ -20157,7 +20720,7 @@ var require_survey_network_guard = __commonJS({
20157
20720
  return { action: "allow", rule: "extra-pattern", reason: extra.reason, record };
20158
20721
  if (graphqlGet)
20159
20722
  return { action: "block", rule: "app-write", record };
20160
- if (method === "POST" && isGraphqlEndpoint(url, s) && isGraphqlQueryOnly(req.postData))
20723
+ if (method === "POST" && isGraphqlEndpoint(url, s) && !graphqlUrlCarriesQuery(url.searchParams) && isGraphqlQueryOnly(req.postData))
20161
20724
  return { action: "allow", rule: "graphql-query", record };
20162
20725
  const passive = s.lastNavigationAt > 0 && (s.lastActionAt === 0 || s.lastNavigationAt >= s.lastActionAt) && s.now - s.lastNavigationAt <= s.passiveWindowMs;
20163
20726
  const signature = learnableSignature(req, url, record, s);
@@ -20189,7 +20752,7 @@ var require_survey_network_guard = __commonJS({
20189
20752
  const re = new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""));
20190
20753
  return { reason, test: (hostPath) => re.test(hostPath) };
20191
20754
  }
20192
- const p = pattern.trim().toLowerCase().replace(/\/+$/, "");
20755
+ const p = (0, shared_1.stripTrailingSlashes)(pattern.trim().toLowerCase());
20193
20756
  if (p.includes("/"))
20194
20757
  return { reason, test: (hostPath) => hostPath.toLowerCase() === p || hostPath.toLowerCase().startsWith(`${p}/`) };
20195
20758
  return { reason, test: (hostPath) => {
@@ -20854,6 +21417,7 @@ var require_run_write_guard = __commonJS({
20854
21417
  exports2.addRunWriteAuthHandler = addRunWriteAuthHandler2;
20855
21418
  exports2.runWriteContextOptions = runWriteContextOptions;
20856
21419
  exports2.clearRunWriteRelay = clearRunWriteRelay2;
21420
+ exports2.runWriteGuardHopBytesInFlight = runWriteGuardHopBytesInFlight;
20857
21421
  exports2.installRunWriteGuard = installRunWriteGuard2;
20858
21422
  exports2.installNodeFetchGuard = installNodeFetchGuard2;
20859
21423
  exports2.runWriteGuardRefusal = runWriteGuardRefusal;
@@ -20865,7 +21429,11 @@ var require_run_write_guard = __commonJS({
20865
21429
  exports2.safeWritePatternFor = safeWritePatternFor;
20866
21430
  var node_async_hooks_1 = require("async_hooks");
20867
21431
  var node_crypto_1 = require("crypto");
21432
+ var node_http_1 = require("http");
21433
+ var node_https_1 = require("https");
20868
21434
  var node_path_1 = require("path");
21435
+ var node_stream_1 = require("stream");
21436
+ var node_zlib_1 = require("zlib");
20869
21437
  var shared_1 = (init_dist(), __toCommonJS(dist_exports));
20870
21438
  var survey_network_guard_js_1 = require_survey_network_guard();
20871
21439
  var bounded_output_js_1 = require_bounded_output();
@@ -20913,12 +21481,13 @@ var require_run_write_guard = __commonJS({
20913
21481
  allowPatterns,
20914
21482
  ignoredAllowPatterns,
20915
21483
  recordOnlyRules,
20916
- reason: v.reason.slice(0, MAX_REASON_CHARS)
21484
+ reason: v.reason.slice(0, MAX_REASON_CHARS),
21485
+ ...v.pwAgentsGuard === "off" ? { pwAgentsGuard: "off" } : {}
20917
21486
  };
20918
21487
  }
20919
21488
  var policyScope = new node_async_hooks_1.AsyncLocalStorage();
20920
21489
  var MIN_SIGN_IN_SECRET_CHARS = 6;
20921
- exports2.SIGN_IN_SECRET_VARIABLE = /PASS(?:WORD|CODE|PHRASE)?$/i;
21490
+ exports2.SIGN_IN_SECRET_VARIABLE = /(?:PASS(?:WORD|CODE|PHRASE)?|[A-Z0-9]_PASSWD|[A-Z0-9]_PWD)$/i;
20922
21491
  function signInSecretsOf(vars) {
20923
21492
  if (!vars || typeof vars !== "object")
20924
21493
  return [];
@@ -20935,6 +21504,7 @@ var require_run_write_guard = __commonJS({
20935
21504
  }
20936
21505
  var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
20937
21506
  var MAX_BODY_CHARS = 262144;
21507
+ var MAX_SIGN_IN_LOG_LINES = 20;
20938
21508
  var CAPTCHA_HOSTS = ["recaptcha.net", "hcaptcha.com", "challenges.cloudflare.com"];
20939
21509
  var SEARCH_POST_HOSTS = ["algolia.net", "algolianet.com", "algolia.io"];
20940
21510
  var PAYMENT_HOSTS = [
@@ -20960,7 +21530,7 @@ var require_run_write_guard = __commonJS({
20960
21530
  var SEARCH_LAST_SEGMENTS = /* @__PURE__ */ new Set(["search", "autocomplete", "suggest", "suggestions", "typeahead"]);
20961
21531
  var PROVIDER_SIGN_UP_PATH = /(?:\/accounts:signup|\/dbconnections\/signup|\/auth\/v1\/signup|\/v1\/client\/sign_ups)(?:[/?]|$)/i;
20962
21532
  var FIRST_PARTY_SIGN_UP_SEGMENTS = /* @__PURE__ */ new Set(["signup", "sign-up", "sign_up", "register", "registration", "create-account", "createaccount", "sign_ups"]);
20963
- var NOT_A_SIGN_IN_SEGMENTS = /* @__PURE__ */ new Set([
21533
+ var NOT_A_SIGN_IN_WORDS = /* @__PURE__ */ new Set([
20964
21534
  "delete",
20965
21535
  "destroy",
20966
21536
  "remove",
@@ -20969,19 +21539,29 @@ var require_run_write_guard = __commonJS({
20969
21539
  "cancel",
20970
21540
  "password",
20971
21541
  "passwords",
20972
- "change-password",
20973
- "change_password",
20974
21542
  "reset",
20975
- "reset-password",
20976
21543
  "update",
20977
21544
  "logout",
20978
21545
  "signout",
20979
- "sign-out",
20980
21546
  "revoke",
20981
21547
  "transfer",
20982
- "withdraw"
21548
+ "withdraw",
21549
+ "disable",
21550
+ "enable",
21551
+ "unlink",
21552
+ "change",
21553
+ "erase",
21554
+ "purge",
21555
+ "wipe",
21556
+ "terminate",
21557
+ "suspend",
21558
+ "leave",
21559
+ "disconnect",
21560
+ "deauthorize"
20983
21561
  ]);
20984
- var SIGN_IN_SHAPED_SEGMENTS = /* @__PURE__ */ new Set([
21562
+ var ACCOUNT_CHANGE_WORDS = /* @__PURE__ */ new Set(["email", "mfa", "2fa", "totp", "otp"]);
21563
+ var ACCOUNT_CHANGE_CONTEXT = /* @__PURE__ */ new Set(["account", "owner", "ownership", "invite"]);
21564
+ var SIGN_IN_LAST_SEGMENTS = /* @__PURE__ */ new Set([
20985
21565
  "session",
20986
21566
  "sessions",
20987
21567
  "login",
@@ -20991,25 +21571,196 @@ var require_run_write_guard = __commonJS({
20991
21571
  "signin",
20992
21572
  "sign-in",
20993
21573
  "sign_in",
20994
- "auth",
20995
21574
  "authenticate",
20996
21575
  "authentication",
20997
- "token",
20998
- "tokens",
20999
- "oauth",
21000
- "oauth2",
21001
- "sso",
21002
- "identity",
21003
- "credentials"
21576
+ "auth"
21004
21577
  ]);
21005
- var GRAPHQL_SIGN_IN = /"operationName"\s*:\s*"(?:log_?in|sign_?in|authenticate|login\w{0,40}|signin\w{0,40})"|\bmutation\b[^{]{0,200}\{\s*(?:log_?in|sign_?in|authenticate|login\w{0,40}|signin\w{0,40})\s*[({]/i;
21006
- function isCredentialSignInShape(pathname, postData) {
21007
- const segs = segmentsOf(pathname);
21008
- if (segs.some((seg) => NOT_A_SIGN_IN_SEGMENTS.has(seg)))
21578
+ var SIGN_IN_VERB_SEGMENTS = /* @__PURE__ */ new Set(["login", "log-in", "log_in", "signin", "sign-in", "sign_in"]);
21579
+ var SIGN_IN_CREDENTIAL_KINDS = /* @__PURE__ */ new Set(["email", "username", "phone", "password"]);
21580
+ var SIGN_IN_TAILS = /* @__PURE__ */ new Set(["callback/credentials", "auth/local", "session/new", "sessions/new"]);
21581
+ var PASSWORD_GRANT = /(?:^|[&{,\s"])grant_type"?\s*[=:]\s*"?password(?=["&,}\s]|$)/;
21582
+ var NEW_CREDENTIAL_STEM = "(?:pass(?:word|wd)?|pwd)";
21583
+ var NEW_CREDENTIAL_WORD = `(?:(?:new|old|current|confirm|repeat|verify|retype)[_-]?(?:${NEW_CREDENTIAL_STEM}|email)|${NEW_CREDENTIAL_STEM}[_-]?(?:new|old|current|confirm(?:ation)?|repeat(?:ed)?|verify|verification|again|retype|check|confirmed|2)|email[_-]?confirm(?:ation)?)`;
21584
+ var NEW_CREDENTIAL_NAME = new RegExp(`^${NEW_CREDENTIAL_WORD}$`, "i");
21585
+ var NEW_CREDENTIAL_KEY = new RegExp(`(?:^|["&{,\\s[])${NEW_CREDENTIAL_WORD}(?:["\\]]?\\s*[:=]|"\\s*(?:;|$))`, "im");
21586
+ var PASSWORD_KEY_WORDS = /* @__PURE__ */ new Set(["password", "passwd", "pwd", "pass"]);
21587
+ var PASSWORD_KEY_QUALIFIERS = /* @__PURE__ */ new Set([
21588
+ "new",
21589
+ "old",
21590
+ "current",
21591
+ "confirm",
21592
+ "confirmed",
21593
+ "confirmation",
21594
+ "repeat",
21595
+ "repeated",
21596
+ "verify",
21597
+ "verification",
21598
+ "again",
21599
+ "retype",
21600
+ "check",
21601
+ "2"
21602
+ ]);
21603
+ function isPasswordKey(key) {
21604
+ const words = wordsOf(key);
21605
+ return words.some((w) => PASSWORD_KEY_WORDS.has(w)) && words.every((w) => PASSWORD_KEY_WORDS.has(w) || PASSWORD_KEY_QUALIFIERS.has(w));
21606
+ }
21607
+ function couldBePassword(value) {
21608
+ if (value === void 0)
21609
+ return true;
21610
+ if (typeof value === "number")
21611
+ return Number.isFinite(value);
21612
+ if (typeof value !== "string")
21009
21613
  return false;
21010
- if (segs.some((seg) => SIGN_IN_SHAPED_SEGMENTS.has(seg)))
21614
+ const v = value.trim();
21615
+ return v !== "" && !/^(?:true|false|on|off|yes|no|null)$/i.test(v);
21616
+ }
21617
+ var MAX_CREDENTIAL_KEYS = 200;
21618
+ function credentialKeysOf(body, contentType) {
21619
+ const type = (contentType ?? "").toLowerCase();
21620
+ const keys = [];
21621
+ try {
21622
+ if (type.includes("json") || /^\s*[[{]/.test(body)) {
21623
+ const stack = [{ value: JSON.parse(body), under: null }];
21624
+ while (stack.length > 0 && keys.length < MAX_CREDENTIAL_KEYS) {
21625
+ const { value, under } = stack.pop();
21626
+ if (Array.isArray(value))
21627
+ stack.push(...value.slice(0, MAX_CREDENTIAL_KEYS).map((inner) => ({ value: inner, under: null })));
21628
+ else if (value && typeof value === "object") {
21629
+ for (const [key, inner] of Object.entries(value).slice(0, MAX_CREDENTIAL_KEYS)) {
21630
+ const name = under === null ? key : `${under}_${key}`;
21631
+ keys.push({ name, value: inner });
21632
+ if (Array.isArray(inner) && isPasswordKey(name)) {
21633
+ for (const item of inner.slice(0, MAX_CREDENTIAL_KEYS))
21634
+ if (item === null || typeof item !== "object")
21635
+ keys.push({ name, value: item });
21636
+ }
21637
+ const object = inner !== null && typeof inner === "object" && !Array.isArray(inner);
21638
+ stack.push({ value: inner, under: object && isPasswordKey(name) ? name : null });
21639
+ }
21640
+ }
21641
+ }
21642
+ } else if (type.includes("multipart/form-data")) {
21643
+ for (const m of body.matchAll(/(?:^|[;\s])name="([^"]{1,200})"/g))
21644
+ if (keys.push({ name: m[1], value: void 0 }) >= MAX_CREDENTIAL_KEYS)
21645
+ break;
21646
+ } else if (type.includes("x-www-form-urlencoded")) {
21647
+ for (const [name, value] of new URLSearchParams(body))
21648
+ if (keys.push({ name, value }) >= MAX_CREDENTIAL_KEYS)
21649
+ break;
21650
+ }
21651
+ } catch {
21652
+ }
21653
+ return keys.slice(0, MAX_CREDENTIAL_KEYS).map(({ name, value }) => {
21654
+ const bare = name.replace(/\[\]$/, "");
21655
+ const parts = bare.match(/[^[\]]+/g);
21656
+ if (bare.endsWith("]") && parts && parts.length > 1) {
21657
+ const inner = parts[parts.length - 1];
21658
+ const outer = parts[parts.length - 2];
21659
+ if (isPasswordKey(outer)) {
21660
+ if (/^\d+$/.test(inner))
21661
+ return { name: outer, value };
21662
+ if (isPasswordKey(inner) || NEW_CREDENTIAL_NAME.test(inner))
21663
+ return { name: inner, value };
21664
+ return { name: `${outer}_${inner}`, value };
21665
+ }
21666
+ }
21667
+ return { name: /\[([^[\]]*)\]$/.exec(bare)?.[1] ?? bare, value };
21668
+ });
21669
+ }
21670
+ function namesNewCredential(body, contentType) {
21671
+ if (NEW_CREDENTIAL_KEY.test(body))
21672
+ return true;
21673
+ const type = contentType ?? (/^\s*[[{]/.test(body) ? null : "application/x-www-form-urlencoded");
21674
+ const keys = credentialKeysOf(body, type);
21675
+ if (keys.some(({ name }) => NEW_CREDENTIAL_NAME.test(name)))
21676
+ return true;
21677
+ const found = keys.filter(({ name, value }) => isPasswordKey(name) && couldBePassword(value));
21678
+ const isBare = (name) => wordsOf(name).every((w) => PASSWORD_KEY_WORDS.has(w));
21679
+ const named = new Set(found.filter(({ name }) => !isBare(name)).map(({ name }) => name.toLowerCase()));
21680
+ const bare = new Set(found.filter(({ name }) => isBare(name)).map(({ name, value }) => value === void 0 ? `name:${name.toLowerCase()}` : `value:${String(value)}`));
21681
+ return named.size + Math.min(bare.size, 2) >= 2;
21682
+ }
21683
+ var GRAPHQL_SIGN_IN_NAME = /^(?:log_?in|sign_?in|authenticate|login\w{0,40}|signin\w{0,40})$/i;
21684
+ function wordsOf(segment) {
21685
+ return segment.replace(/([a-z])(?=[A-Z0-9])/g, "$1 ").toLowerCase().split(/[\s._/-]+/).filter(Boolean);
21686
+ }
21687
+ function rawSegmentsOf(pathname) {
21688
+ return pathname.split("/").map((seg) => {
21689
+ const raw = seg.split(";")[0];
21690
+ try {
21691
+ return decodeURIComponent(raw);
21692
+ } catch {
21693
+ return raw;
21694
+ }
21695
+ }).filter(Boolean);
21696
+ }
21697
+ function changesSomething(segments, opts = {}) {
21698
+ const words = segments.flatMap(wordsOf);
21699
+ const joined = segments.map((seg) => wordsOf(seg).join(""));
21700
+ const hard = (w) => NOT_A_SIGN_IN_WORDS.has(w) && !(opts.passwordIsCredential && (w === "password" || w === "passwords"));
21701
+ if (words.some(hard) || joined.some(hard))
21702
+ return true;
21703
+ return words.some((w) => ACCOUNT_CHANGE_WORDS.has(w)) && words.some((w) => ACCOUNT_CHANGE_CONTEXT.has(w));
21704
+ }
21705
+ function isGraphqlSignInName(name) {
21706
+ return GRAPHQL_SIGN_IN_NAME.test(name) && !changesSomething([name], { passwordIsCredential: true });
21707
+ }
21708
+ function graphqlSignIn(body) {
21709
+ let parsed;
21710
+ try {
21711
+ parsed = JSON.parse(body);
21712
+ } catch {
21713
+ parsed = { query: body };
21714
+ }
21715
+ const entries = Array.isArray(parsed) ? parsed : [parsed];
21716
+ let signIns = 0;
21717
+ for (const entry of entries) {
21718
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
21719
+ return null;
21720
+ const { query, operationName } = entry;
21721
+ if (typeof query !== "string") {
21722
+ if (!Array.isArray(parsed) && query === void 0 && typeof operationName === "string" && isGraphqlSignInName(operationName))
21723
+ return "persisted";
21724
+ return null;
21725
+ }
21726
+ const ops = (0, survey_network_guard_js_1.graphqlOperationRoots)(query);
21727
+ if (!ops)
21728
+ return null;
21729
+ for (const op of ops) {
21730
+ if (op.kind === "query")
21731
+ continue;
21732
+ if (typeof operationName === "string" && changesSomething([operationName], { passwordIsCredential: true }))
21733
+ return null;
21734
+ const roots = op.roots.filter((root) => root !== "__typename");
21735
+ if (roots.length === 0 || !roots.every(isGraphqlSignInName))
21736
+ return null;
21737
+ signIns += roots.length;
21738
+ }
21739
+ }
21740
+ return signIns > 0 ? "document" : null;
21741
+ }
21742
+ function isCredentialSignInShape(pathname, search, postData, contentType) {
21743
+ if (typeof postData === "string" && postData.length > MAX_BODY_CHARS)
21744
+ return false;
21745
+ const body = typeof postData === "string" && postData.length <= MAX_BODY_CHARS ? postData : null;
21746
+ if (body !== null && namesNewCredential(body, contentType))
21747
+ return false;
21748
+ const raw = rawSegmentsOf(pathname);
21749
+ const segs = raw.map((seg) => seg.toLowerCase());
21750
+ const last = segs[segs.length - 1] ?? "";
21751
+ const prev = segs[segs.length - 2] ?? "";
21752
+ if (SIGN_IN_VERB_SEGMENTS.has(prev) && SIGN_IN_CREDENTIAL_KINDS.has(last))
21753
+ return !changesSomething(raw, { passwordIsCredential: true });
21754
+ if (changesSomething(raw))
21755
+ return false;
21756
+ if (SIGN_IN_LAST_SEGMENTS.has(last) || SIGN_IN_TAILS.has(`${prev}/${last}`))
21011
21757
  return true;
21012
- return isGraphqlPath(pathname) && typeof postData === "string" && postData.length <= MAX_BODY_CHARS && GRAPHQL_SIGN_IN.test(postData);
21758
+ if (last === "token")
21759
+ return search.get("grant_type") === "password" || body !== null && PASSWORD_GRANT.test(body);
21760
+ if (!isGraphqlPath(pathname) || body === null || (0, survey_network_guard_js_1.graphqlUrlCarriesQuery)(search))
21761
+ return false;
21762
+ const graphql = graphqlSignIn(body);
21763
+ return graphql === "persisted" ? graphql : graphql === "document";
21013
21764
  }
21014
21765
  function bodyHasSignInSecret(body, secrets) {
21015
21766
  if (!body || body.length > MAX_BODY_CHARS || secrets.length === 0)
@@ -21037,7 +21788,7 @@ var require_run_write_guard = __commonJS({
21037
21788
  var COGNITO_HOSTED_UI_RE = /^\/(?:oauth2\/(?:token|authorize|userinfo|idpresponse)|saml2\/idpresponse|login|logincontinue)$/;
21038
21789
  var CLERK_FAPI_RE = /^\/v1\/(?:client(?:\/(?:sign_ins(?:\/[^/]+\/(?:prepare_first_factor|attempt_first_factor|prepare_second_factor|attempt_second_factor))?|sessions\/[^/]+\/(?:touch|tokens(?:\/[^/]+)?)|handshake|sync|link))?|dev_browser|environment|oauth_callback)$/;
21039
21790
  var SUPABASE_AUTH_RE = /^\/auth\/v1\/(?:token|otp|magiclink|verify|authorize|callback|sso|saml\/acs|reauthenticate|factors\/[^/]+\/(?:challenge|verify))$/;
21040
- var AUTH0_AUTH_RE = /^\/(?:oauth\/(?:token|ro)|authorize(?:\/resume)?|co\/authenticate|usernamepassword\/(?:login|challenge)|login\/(?:callback|identifier|password)|passwordless\/(?:start|verify)|userinfo|mfa\/challenge|u\/(?:login(?:\/[^/]+)?|mfa-(?:otp|sms|email|voice|phone|push|webauthn-platform|webauthn-roaming|recovery-code)-challenge(?:-push|-new-code)?|mfa-login-options|mfa-detect-browser-capabilities|mfa-country-codes|consent))$/;
21791
+ var AUTH0_AUTH_RE = /^\/(?:oauth\/(?:token|ro)|authorize(?:\/resume)?|co\/authenticate|usernamepassword\/(?:login|challenge)|login\/(?:callback|identifier|password)|passwordless\/(?:start|verify)|userinfo|mfa\/challenge|u\/(?:login(?:\/[^/]+)?|mfa-(?:otp|sms|email|voice|phone|push|webauthn-platform|webauthn-roaming|recovery-code)-challenge(?:-push|-new-code)?|mfa-(?:sms|push|email)-list|mfa-login-options|mfa-detect-browser-capabilities|mfa-country-codes|consent))$/;
21041
21792
  var OKTA_AUTH_RE = /^\/(?:api\/v1\/(?:authn(?:\/(?:factors\/[^/]+\/verify|skip|cancel))?|sessions(?:\/me\/lifecycle\/refresh)?)|oauth2\/(?:[^/]+\/)?v1\/(?:token|authorize|introspect|userinfo|device\/authorize)|idp\/idx\/(?:introspect|identify(?:\/select)?|challenge(?:\/answer|\/poll)?|skip|cancel)|login\/[^/]+(?:\/[^/]+)?)$/;
21042
21793
  var MICROSOFT_AUTH_RE = /\/(?:oauth2\/(?:v2\.0\/)?(?:token|authorize|devicecode)|login|kmsi|getcredentialtype|sas\/(?:processauth|beginauth|endauth)|selfasserted|api\/combinedsigninandsignup\/[^/]+|resume|reprocess|federation\/oauth2)$/;
21043
21794
  var GOOGLE_ACCOUNTS_RE = /^\/(?:o\/oauth2\/(?:token|auth|v2\/auth|iframerpc|postmessagerelay|approval)|gsi\/[^/]+(?:\/[^/]+)?|_\/(?:signin|lookup|identitysigninui|accountssigninui)\/.+|v3\/signin\/.+|signin\/.+|servicelogin|checkcookie|accountchooser)$/;
@@ -21103,14 +21854,26 @@ var require_run_write_guard = __commonJS({
21103
21854
  return GENERIC_IDP_AUTH_RE.test(path);
21104
21855
  }
21105
21856
  function isAuthProviderWrite(method, host, pathname, amzTarget, search) {
21857
+ if (!isPlainPost(method, search))
21858
+ return false;
21859
+ if (!(0, shared_1.isAuthProviderHost)(host))
21860
+ return false;
21861
+ return isAuthProviderSignIn(host.toLowerCase(), pathname, amzTarget, search);
21862
+ }
21863
+ function isPlainPost(method, search) {
21106
21864
  if (method.toUpperCase() !== "POST")
21107
21865
  return false;
21108
21866
  const tunnelled = search.get("_method");
21109
- if (tunnelled !== null && tunnelled.toUpperCase() !== "POST")
21867
+ return tunnelled === null || tunnelled.toUpperCase() === "POST";
21868
+ }
21869
+ function isAuth0CustomDomainSignIn(method, pathname, search) {
21870
+ if (!isPlainPost(method, search))
21110
21871
  return false;
21111
- if (!(0, shared_1.isAuthProviderHost)(host))
21872
+ const path = pathname.toLowerCase();
21873
+ if (!AUTH0_AUTH_RE.test(path))
21112
21874
  return false;
21113
- return isAuthProviderSignIn(host.toLowerCase(), pathname, amzTarget, search);
21875
+ const needsState = path.startsWith("/u/") || path === "/authorize" || path === "/authorize/resume";
21876
+ return !needsState || (search.get("state") ?? "") !== "";
21114
21877
  }
21115
21878
  function isSignUp(host, pathname, amzTarget) {
21116
21879
  if (PROVIDER_SIGN_UP_PATH.test(pathname))
@@ -21236,8 +21999,7 @@ var require_run_write_guard = __commonJS({
21236
21999
  const host = url.hostname.toLowerCase().replace(/\.$/, "");
21237
22000
  const pathname = url.pathname;
21238
22001
  if (READ_METHODS.has(method)) {
21239
- const doc = method === "GET" && isGraphqlPath(pathname) ? url.searchParams.get("query") : null;
21240
- if (doc !== null && !(0, survey_network_guard_js_1.graphqlDocumentIsQueryOnly)(doc)) {
22002
+ if (method === "GET" && isGraphqlPath(pathname) && (0, survey_network_guard_js_1.graphqlGetMayWrite)(url, facts.postData)) {
21241
22003
  return { action: "write", rule: "graphql-get-mutation", record: describe(facts, url, "graphql-get-mutation") };
21242
22004
  }
21243
22005
  if (method !== "OPTIONS" && isAppSite(host, policy)) {
@@ -21270,15 +22032,17 @@ var require_run_write_guard = __commonJS({
21270
22032
  return { action: "allow", rule: "auth-provider" };
21271
22033
  if (appSite && (0, survey_network_guard_js_1.isSignInPathWrite)(method, pathname))
21272
22034
  return { action: "allow", rule: "sign-in" };
21273
- if (appSite && method === "POST" && facts.brokerTokenInBody && !segmentsOf(pathname).some((seg) => NOT_A_SIGN_IN_SEGMENTS.has(seg))) {
22035
+ if (appSite && isAuth0CustomDomainSignIn(method, pathname, url.searchParams))
22036
+ return { action: "allow", rule: "sign-in" };
22037
+ if (appSite && method === "POST" && facts.brokerTokenInBody && !changesSomething(rawSegmentsOf(pathname))) {
21274
22038
  return { action: "allow", rule: "broker-sign-in" };
21275
22039
  }
21276
22040
  if (appSite && isEvidencedTokenRefresh(method, pathname, facts.postData))
21277
22041
  return { action: "allow", rule: "token-refresh" };
21278
- if (appSite && method === "POST" && facts.signInSecretInBody && isCredentialSignInShape(pathname, facts.postData)) {
21279
- return { action: "allow", rule: "credential-sign-in" };
21280
- }
21281
- if (method === "POST" && isGraphqlPath(pathname) && isGraphqlQueryOnlyBody(facts.postData))
22042
+ const signIn = appSite && method === "POST" && facts.signInSecretInBody ? isCredentialSignInShape(pathname, url.searchParams, facts.postData, facts.contentType) : false;
22043
+ if (signIn)
22044
+ return { action: "allow", rule: "credential-sign-in", ...signIn === "persisted" ? { persisted: true } : {} };
22045
+ if (method === "POST" && isGraphqlPath(pathname) && !(0, survey_network_guard_js_1.graphqlUrlCarriesQuery)(url.searchParams) && isGraphqlQueryOnlyBody(facts.postData))
21282
22046
  return { action: "allow", rule: "graphql-query" };
21283
22047
  if (method === "POST" && hostIs(host, SEARCH_POST_HOSTS))
21284
22048
  return { action: "allow", rule: "search-post" };
@@ -21323,6 +22087,8 @@ var require_run_write_guard = __commonJS({
21323
22087
  recordOnly;
21324
22088
  log;
21325
22089
  signInSecrets;
22090
+ /** The deadline of one request the guard sends itself, all its hops together (sendCheckingRedirect). */
22091
+ redirectCheckTimeoutMs;
21326
22092
  /** Tokens the Google sign-in broker handed this context, each until its expiry (epoch ms). */
21327
22093
  brokerTokens = [];
21328
22094
  /** Origins of the Firebase Auth handlers the broker registered for this context. */
@@ -21331,12 +22097,16 @@ var require_run_write_guard = __commonJS({
21331
22097
  relayIds = /* @__PURE__ */ new Set();
21332
22098
  relayed = 0;
21333
22099
  relayRejected = 0;
22100
+ /** Credential sign-ins already said in the run log (observe mode; method, host and path shape). */
22101
+ signInsLogged = /* @__PURE__ */ new Set();
21334
22102
  constructor(policy, opts = {}) {
21335
22103
  this.policy = policy;
21336
22104
  this.compiled = policy.allowPatterns.map(compileAllowPattern).filter((p) => p !== null);
21337
22105
  this.recordOnly = new Set((policy.recordOnlyRules ?? exports2.DEFAULT_RECORD_ONLY_RULES).filter((rule) => exports2.RECORD_ONLY_CAPABLE_RULES.includes(rule)));
21338
22106
  this.log = opts.onLog;
21339
22107
  this.signInSecrets = (opts.signInSecrets ?? []).filter((v) => typeof v === "string" && v.length >= MIN_SIGN_IN_SECRET_CHARS);
22108
+ const ms = opts.redirectCheckTimeoutMs;
22109
+ this.redirectCheckTimeoutMs = typeof ms === "number" && ms > 0 ? Math.min(ms, REDIRECT_CHECK_TIMEOUT_MS) : REDIRECT_CHECK_TIMEOUT_MS;
21340
22110
  }
21341
22111
  decide(facts) {
21342
22112
  const signInSecretInBody = facts.signInSecretInBody ?? bodyHasSignInSecret(facts.postData, this.signInSecrets);
@@ -21359,8 +22129,28 @@ var require_run_write_guard = __commonJS({
21359
22129
  consumedBrokerTokens.add(brokerToken.value);
21360
22130
  this.brokerTokens = this.brokerTokens.filter((t) => t !== brokerToken);
21361
22131
  }
22132
+ if (decision.rule === "credential-sign-in" && this.policy.mode === "observe")
22133
+ this.noteCredentialSignIn(facts, decision.persisted === true);
21362
22134
  return decision;
21363
22135
  }
22136
+ /**
22137
+ * Observe mode says where each credential sign-in went, once per path and kind (the lead): the
22138
+ * method, host and masked path only, never the body, so the rule can be checked against real runs
22139
+ * before a site is blocked. A GraphQL persisted query is marked unverified.
22140
+ */
22141
+ noteCredentialSignIn(facts, persisted) {
22142
+ let host = "";
22143
+ try {
22144
+ host = new URL(facts.url).hostname.toLowerCase();
22145
+ } catch {
22146
+ return;
22147
+ }
22148
+ const line = `${facts.method.toUpperCase()} ${host}${(0, survey_network_guard_js_1.maskedPath)(facts.url)} [credential-sign-in${persisted ? " (persisted, unverified)" : ""}]`;
22149
+ if (this.signInsLogged.has(line) || this.signInsLogged.size >= MAX_SIGN_IN_LOG_LINES)
22150
+ return;
22151
+ this.signInsLogged.add(line);
22152
+ this.log?.(`[run-write-guard] allowed ${line}`);
22153
+ }
21364
22154
  /**
21365
22155
  * A code or ID token the Google sign-in broker hands this context: the first POST that carries it
21366
22156
  * to the app in the next BROKER_TOKEN_TTL_MS is the sign-in. Anything not shaped like a Google code
@@ -21420,8 +22210,9 @@ var require_run_write_guard = __commonJS({
21420
22210
  }
21421
22211
  /**
21422
22212
  * A hop the guard fetched itself (sendCheckingRedirect) failed, so the request fails in the page.
21423
- * Said in the run log, with the timeout when it was that: a slow allowed upload or stream is cut
21424
- * at REDIRECT_CHECK_TIMEOUT_MS, and the page only sees a network error.
22213
+ * Said in the run log, with the reason when it was the guard's own limit: a slow allowed upload or
22214
+ * stream is cut at its deadline, an answer over MAX_GUARD_HOP_BYTES as it streams in, and the page
22215
+ * only sees a network error.
21425
22216
  */
21426
22217
  noteRedirectCheckFailed(facts, err, timeoutMs) {
21427
22218
  let host = "";
@@ -21429,8 +22220,8 @@ var require_run_write_guard = __commonJS({
21429
22220
  host = new URL(facts.url).hostname.toLowerCase();
21430
22221
  } catch {
21431
22222
  }
21432
- const timedOut = err instanceof Error && (err.name === "TimeoutError" || /\btimeout\b/i.test(err.message));
21433
- const why = timedOut ? `the guard's redirect check timed out after ${Math.round(timeoutMs / 1e3)} s (REDIRECT_CHECK_TIMEOUT_MS)` : "the guard's redirect check failed";
22223
+ const reason = err instanceof GuardHopError ? err.reason : null;
22224
+ const why = reason === "timeout" ? `the guard's redirect check timed out after ${Math.round(timeoutMs / 1e3)} s (REDIRECT_CHECK_TIMEOUT_MS)` : reason === "over-cap" ? `the answer was over ${MAX_GUARD_HOP_BYTES / (1024 * 1024)} MB (MAX_GUARD_HOP_BYTES), cut as it came in` : "the guard's redirect check failed";
21434
22225
  this.log?.(`[run-write-guard] note: ${facts.method.toUpperCase()} ${host}${(0, survey_network_guard_js_1.maskedPath)(facts.url)} failed in the page: ${why}`);
21435
22226
  }
21436
22227
  /** A request still undecided when its page closed (see guardPageClose): it is failed in the page, so the browser never sends it. */
@@ -21878,66 +22669,321 @@ var require_run_write_guard = __commonJS({
21878
22669
  }
21879
22670
  }
21880
22671
  var MAX_GUARD_REDIRECTS = 5;
21881
- var REDIRECT_CHECK_TIMEOUT_MS = 6e4;
22672
+ var REDIRECT_CHECK_TIMEOUT_MS = 3e4;
22673
+ var MAX_GUARD_HOP_BYTES = 16 * 1024 * 1024;
22674
+ var MAX_GUARD_BUFFERED_BYTES = 128 * 1024 * 1024;
22675
+ var SEND_HTTP = node_http_1.request;
22676
+ var SEND_HTTPS = node_https_1.request;
22677
+ var HOP_ONLY_HEADERS = /* @__PURE__ */ new Set([
22678
+ "connection",
22679
+ "keep-alive",
22680
+ "proxy-connection",
22681
+ "transfer-encoding",
22682
+ "te",
22683
+ "trailer",
22684
+ "upgrade",
22685
+ "proxy-authorization",
22686
+ "proxy-authenticate",
22687
+ "host",
22688
+ "content-length"
22689
+ ]);
22690
+ var hopBytesInFlight = 0;
22691
+ var hopsInFlight = /* @__PURE__ */ new Set();
22692
+ var hopBudgetWaiters = [];
22693
+ function releaseHopBytes(hop, bytes) {
22694
+ hopsInFlight.delete(hop);
22695
+ hopBytesInFlight -= bytes;
22696
+ for (const wake of hopBudgetWaiters.splice(0))
22697
+ wake();
22698
+ }
22699
+ function runWriteGuardHopBytesInFlight() {
22700
+ return hopBytesInFlight;
22701
+ }
22702
+ var GuardHopError = class extends Error {
22703
+ reason;
22704
+ constructor(reason, message) {
22705
+ super(message);
22706
+ this.reason = reason;
22707
+ this.name = "GuardHopError";
22708
+ }
22709
+ };
22710
+ function sendGuardHop(hop) {
22711
+ return new Promise((resolve, reject) => {
22712
+ let url;
22713
+ try {
22714
+ url = new URL(hop.url);
22715
+ } catch (err) {
22716
+ reject(err);
22717
+ return;
22718
+ }
22719
+ const send = url.protocol === "https:" ? SEND_HTTPS : url.protocol === "http:" ? SEND_HTTP : null;
22720
+ if (!send) {
22721
+ reject(new Error(`the guard sends only http(s), not ${url.protocol}`));
22722
+ return;
22723
+ }
22724
+ const id = /* @__PURE__ */ Symbol("guard hop");
22725
+ hopsInFlight.add(id);
22726
+ let held = 0;
22727
+ let settled = false;
22728
+ let req = null;
22729
+ const chunks = [];
22730
+ const fail = (err) => {
22731
+ if (settled)
22732
+ return;
22733
+ settled = true;
22734
+ clearTimeout(timer);
22735
+ req?.destroy();
22736
+ releaseHopBytes(id, held);
22737
+ held = 0;
22738
+ reject(err);
22739
+ };
22740
+ const overCap = () => new GuardHopError("over-cap", `answer over ${MAX_GUARD_HOP_BYTES} bytes`);
22741
+ const timer = setTimeout(() => fail(new GuardHopError("timeout", "the guard hop passed its deadline")), Math.max(0, hop.deadline - Date.now()));
22742
+ try {
22743
+ req = send(url, { method: hop.method, headers: hop.headers, rejectUnauthorized: !hop.ignoreHTTPSErrors, agent: false }, (res) => {
22744
+ const encoding = String(res.headers["content-encoding"] ?? "").trim().toLowerCase();
22745
+ const decoder = encoding === "gzip" || encoding === "x-gzip" ? (0, node_zlib_1.createGunzip)({ flush: node_zlib_1.constants.Z_SYNC_FLUSH, finishFlush: node_zlib_1.constants.Z_SYNC_FLUSH }) : encoding === "br" ? (0, node_zlib_1.createBrotliDecompress)({ flush: node_zlib_1.constants.BROTLI_OPERATION_FLUSH, finishFlush: node_zlib_1.constants.BROTLI_OPERATION_FLUSH }) : encoding === "deflate" ? (0, node_zlib_1.createInflate)() : null;
22746
+ const body = decoder ? (0, node_stream_1.pipeline)(res, decoder, (err) => {
22747
+ if (err)
22748
+ fail(err);
22749
+ }) : res;
22750
+ const parked = [];
22751
+ let paused = false;
22752
+ let ended = false;
22753
+ const take = (chunk) => {
22754
+ if (settled)
22755
+ return;
22756
+ if (paused) {
22757
+ parked.push(chunk);
22758
+ return;
22759
+ }
22760
+ if (held + chunk.length > MAX_GUARD_HOP_BYTES) {
22761
+ fail(overCap());
22762
+ return;
22763
+ }
22764
+ const oldest = hopsInFlight.values().next().value === id;
22765
+ const room = oldest ? MAX_GUARD_BUFFERED_BYTES : MAX_GUARD_BUFFERED_BYTES - MAX_GUARD_HOP_BYTES;
22766
+ if (hopBytesInFlight + chunk.length > room) {
22767
+ paused = true;
22768
+ body.pause();
22769
+ parked.push(chunk);
22770
+ hopBudgetWaiters.push(drain);
22771
+ return;
22772
+ }
22773
+ hopBytesInFlight += chunk.length;
22774
+ held += chunk.length;
22775
+ chunks.push(chunk);
22776
+ };
22777
+ const drain = () => {
22778
+ paused = false;
22779
+ const queued = parked.splice(0);
22780
+ for (let i = 0; i < queued.length; i++) {
22781
+ take(queued[i]);
22782
+ if (settled)
22783
+ return;
22784
+ if (paused) {
22785
+ parked.push(...queued.slice(i + 1));
22786
+ return;
22787
+ }
22788
+ }
22789
+ if (ended)
22790
+ finish();
22791
+ else
22792
+ body.resume();
22793
+ };
22794
+ body.on("data", take);
22795
+ body.on("error", (err) => fail(err));
22796
+ res.on("error", (err) => fail(err));
22797
+ body.on("end", () => {
22798
+ ended = true;
22799
+ if (!paused)
22800
+ finish();
22801
+ });
22802
+ const finish = () => {
22803
+ if (settled)
22804
+ return;
22805
+ settled = true;
22806
+ clearTimeout(timer);
22807
+ const kept = held;
22808
+ let released = false;
22809
+ const headers = [];
22810
+ for (let i = 0; i + 1 < res.rawHeaders.length; i += 2)
22811
+ headers.push([res.rawHeaders[i], res.rawHeaders[i + 1]]);
22812
+ resolve({
22813
+ status: res.statusCode ?? 0,
22814
+ headers,
22815
+ body: Buffer.concat(chunks, kept),
22816
+ decoded: decoder !== null,
22817
+ release: () => {
22818
+ if (!released) {
22819
+ released = true;
22820
+ releaseHopBytes(id, kept);
22821
+ }
22822
+ }
22823
+ });
22824
+ };
22825
+ });
22826
+ req.on("error", (err) => fail(err));
22827
+ req.end(hop.body ?? void 0);
22828
+ } catch (err) {
22829
+ fail(err instanceof Error ? err : new Error(String(err)));
22830
+ }
22831
+ });
22832
+ }
22833
+ function contextIgnoresHttpsErrors(context) {
22834
+ try {
22835
+ return context._options?.ignoreHTTPSErrors === true;
22836
+ } catch {
22837
+ return false;
22838
+ }
22839
+ }
22840
+ function hopRequestHeaders(pageHeaders, cookie) {
22841
+ const out = { accept: "*/*" };
22842
+ for (const [name, value] of Object.entries(pageHeaders)) {
22843
+ const key = name.toLowerCase();
22844
+ if (key.startsWith(":") || HOP_ONLY_HEADERS.has(key) || key === "cookie")
22845
+ continue;
22846
+ out[key] = value;
22847
+ }
22848
+ out["accept-encoding"] = "gzip, deflate, br";
22849
+ if (cookie)
22850
+ out.cookie = cookie;
22851
+ return out;
22852
+ }
22853
+ function cookiePairs(header) {
22854
+ return header.split(";").flatMap((part) => {
22855
+ const eq = part.indexOf("=");
22856
+ return eq > 0 ? [[part.slice(0, eq).trim(), part.slice(eq + 1).trim()]] : [];
22857
+ });
22858
+ }
22859
+ function hopCookie(pageCookie, contextCookies, setCookies) {
22860
+ const jar = /* @__PURE__ */ new Map();
22861
+ if (pageCookie)
22862
+ for (const [name, value] of cookiePairs(pageCookie))
22863
+ jar.set(name, value);
22864
+ else
22865
+ for (const c of contextCookies)
22866
+ jar.set(c.name, c.value);
22867
+ for (const line of setCookies) {
22868
+ const [pair, ...attrs] = line.split(";");
22869
+ const eq = pair.indexOf("=");
22870
+ if (eq <= 0)
22871
+ continue;
22872
+ const name = pair.slice(0, eq).trim();
22873
+ const gone = attrs.some((a) => {
22874
+ const [k, v = ""] = a.split("=");
22875
+ const key = k.trim().toLowerCase();
22876
+ return key === "max-age" && Number(v.trim()) <= 0 || key === "expires" && Date.parse(v.trim()) < Date.now();
22877
+ });
22878
+ if (gone)
22879
+ jar.delete(name);
22880
+ else
22881
+ jar.set(name, pair.slice(eq + 1).trim());
22882
+ }
22883
+ return [...jar].map(([name, value]) => `${name}=${value}`).join("; ");
22884
+ }
22885
+ function setCookiesOf(answer) {
22886
+ return answer.headers.filter(([name]) => name.toLowerCase() === "set-cookie").map(([, value]) => value);
22887
+ }
22888
+ function fulfillHeaders(answer, earlierSetCookies) {
22889
+ const out = {};
22890
+ const cookies = [...earlierSetCookies];
22891
+ for (const [name, value] of answer.headers) {
22892
+ const key = name.toLowerCase();
22893
+ if (HOP_ONLY_HEADERS.has(key) || answer.decoded && key === "content-encoding")
22894
+ continue;
22895
+ if (key === "set-cookie") {
22896
+ cookies.push(value);
22897
+ continue;
22898
+ }
22899
+ out[key] = key in out ? `${out[key]}, ${value}` : value;
22900
+ }
22901
+ if (cookies.length > 0)
22902
+ out["set-cookie"] = cookies.join("\n");
22903
+ return out;
22904
+ }
21882
22905
  var cutAtClose = /* @__PURE__ */ new WeakSet();
21883
- async function sendCheckingRedirect(route, guard) {
21884
- const first = factsOf(route.request());
22906
+ async function sendCheckingRedirect(route, guard, context) {
22907
+ const request = route.request();
22908
+ const first = factsOf(request);
22909
+ const deadline = Date.now() + guard.redirectCheckTimeoutMs;
21885
22910
  let facts = first;
22911
+ let pageHeaders = {};
22912
+ let postBody = null;
22913
+ const setCookies = [];
21886
22914
  for (let hop = 0; ; hop++) {
21887
- let response;
22915
+ let answer;
21888
22916
  try {
21889
- response = await route.fetch(hop === 0 ? { maxRedirects: 0, timeout: REDIRECT_CHECK_TIMEOUT_MS } : { url: facts.url, maxRedirects: 0, timeout: REDIRECT_CHECK_TIMEOUT_MS });
22917
+ if (hop === 0) {
22918
+ pageHeaders = request.headers();
22919
+ postBody = request.postDataBuffer();
22920
+ }
22921
+ const pageCookie = hop === 0 ? Object.entries(pageHeaders).find(([name]) => name.toLowerCase() === "cookie")?.[1] : void 0;
22922
+ const cookie = hopCookie(pageCookie, pageCookie ? [] : await context.cookies(facts.url), setCookies);
22923
+ answer = await sendGuardHop({
22924
+ url: facts.url,
22925
+ method: facts.method,
22926
+ headers: hopRequestHeaders(pageHeaders, cookie),
22927
+ body: postBody,
22928
+ ignoreHTTPSErrors: contextIgnoresHttpsErrors(context),
22929
+ deadline
22930
+ });
21890
22931
  } catch (err) {
21891
22932
  if (cutAtClose.has(route))
21892
22933
  return;
21893
- guard.noteRedirectCheckFailed(facts, err, REDIRECT_CHECK_TIMEOUT_MS);
22934
+ guard.noteRedirectCheckFailed(facts, err, guard.redirectCheckTimeoutMs);
21894
22935
  await route.abort("failed").catch(() => {
21895
22936
  });
21896
22937
  return;
21897
22938
  }
21898
- if (cutAtClose.has(route))
21899
- return;
21900
- const status = response.status();
21901
- const location = response.headers()["location"];
21902
- const next = REDIRECT_STATUSES.has(status) && location ? redirectFacts(facts, status, location) : null;
21903
- if (!next) {
21904
- await route.fulfill({ response }).catch(() => {
21905
- });
21906
- return;
21907
- }
21908
- let decision;
21909
22939
  try {
21910
- decision = guard.decide(next);
21911
- } catch {
21912
- decision = { action: "write", rule: "decision-error", record: { method: next.method, host: "", path: (0, survey_network_guard_js_1.maskedPath)(next.url), keys: [], rule: "decision-error" } };
21913
- }
21914
- if (decision.action === "write") {
21915
- if (decision.record)
21916
- guard.note(decision.record);
21917
- if (guard.holds(decision.rule)) {
22940
+ if (cutAtClose.has(route))
22941
+ return;
22942
+ const status = answer.status;
22943
+ const location = answer.headers.find(([name]) => name.toLowerCase() === "location")?.[1];
22944
+ const next = REDIRECT_STATUSES.has(status) && location ? redirectFacts(facts, status, location) : null;
22945
+ if (!next) {
22946
+ await route.fulfill({ status, headers: fulfillHeaders(answer, setCookies), body: answer.body }).catch(() => {
22947
+ });
22948
+ return;
22949
+ }
22950
+ let decision;
22951
+ try {
22952
+ decision = guard.decide(next);
22953
+ } catch {
22954
+ decision = { action: "write", rule: "decision-error", record: { method: next.method, host: "", path: (0, survey_network_guard_js_1.maskedPath)(next.url), keys: [], rule: "decision-error" } };
22955
+ }
22956
+ if (decision.action === "write") {
22957
+ if (decision.record)
22958
+ guard.note(decision.record);
22959
+ if (guard.holds(decision.rule)) {
22960
+ await route.abort("blockedbyclient").catch(() => {
22961
+ });
22962
+ return;
22963
+ }
22964
+ }
22965
+ if (READ_METHODS.has(next.method.toUpperCase())) {
22966
+ await route.fulfill({ status, headers: { ...fulfillHeaders(answer, setCookies), location: next.url }, body: answer.body }).catch(() => {
22967
+ });
22968
+ return;
22969
+ }
22970
+ if (!sameOrigin(next.url, first.url)) {
22971
+ const target = new URL(next.url);
22972
+ guard.note(describe(next, target, isAppSite(target.hostname.toLowerCase(), guard.policy) ? "app-write" : "third-party-write"));
21918
22973
  await route.abort("blockedbyclient").catch(() => {
21919
22974
  });
21920
22975
  return;
21921
22976
  }
22977
+ if (hop + 1 >= MAX_GUARD_REDIRECTS) {
22978
+ await route.abort("failed").catch(() => {
22979
+ });
22980
+ return;
22981
+ }
22982
+ setCookies.push(...setCookiesOf(answer));
22983
+ facts = next;
22984
+ } finally {
22985
+ answer.release();
21922
22986
  }
21923
- if (READ_METHODS.has(next.method.toUpperCase())) {
21924
- await route.fulfill({ response, headers: { ...response.headers(), location: next.url } }).catch(() => {
21925
- });
21926
- return;
21927
- }
21928
- if (!sameOrigin(next.url, first.url)) {
21929
- const target = new URL(next.url);
21930
- guard.note(describe(next, target, isAppSite(target.hostname.toLowerCase(), guard.policy) ? "app-write" : "third-party-write"));
21931
- await route.abort("blockedbyclient").catch(() => {
21932
- });
21933
- return;
21934
- }
21935
- if (hop + 1 >= MAX_GUARD_REDIRECTS) {
21936
- await route.abort("failed").catch(() => {
21937
- });
21938
- return;
21939
- }
21940
- facts = next;
21941
22987
  }
21942
22988
  }
21943
22989
  function sameRouteUrl(a, b) {
@@ -22151,7 +23197,7 @@ var require_run_write_guard = __commonJS({
22151
23197
  }
22152
23198
  }
22153
23199
  if (decision.action === "allow" && !READ_METHODS.has(request.method().toUpperCase()) && checksRedirect(decision.rule, request.url(), guard.policy)) {
22154
- await sendCheckingRedirect(route, guard);
23200
+ await sendCheckingRedirect(route, guard, context);
22155
23201
  return;
22156
23202
  }
22157
23203
  await route.fallback().catch(() => {