@validate.qa/runner 1.0.103 → 1.0.104

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +7 -2
  2. package/dist/{chunk-YI3ZC2XI.mjs → chunk-6N4QCCEH.mjs} +2 -2
  3. package/dist/{chunk-CMM4DOXC.mjs → chunk-EBWZ7NGU.mjs} +11 -8
  4. package/dist/{chunk-OTKJZIOD.mjs → chunk-EKY6CN4Y.mjs} +659 -122
  5. package/dist/{chunk-CG75I3ZZ.mjs → chunk-MEE4DXRX.mjs} +13437 -1386
  6. package/dist/{chunk-WVLBQDFP.mjs → chunk-OXDU6ARE.mjs} +8 -2
  7. package/dist/{chunk-AAUHXGDS.mjs → chunk-PUMRAFJS.mjs} +37 -13
  8. package/dist/{chunk-BECCGMU4.mjs → chunk-TGVKABFN.mjs} +45 -3
  9. package/dist/{chunk-YIM746WO.mjs → chunk-TS4OUJVT.mjs} +1 -1
  10. package/dist/{chunk-NNJYKR3O.mjs → chunk-UR7KDOTP.mjs} +29 -6
  11. package/dist/{chunk-36AWDKPL.mjs → chunk-UZ67UBXH.mjs} +14 -3
  12. package/dist/{chunk-7SH3COMQ.mjs → chunk-VWE3SQ2Z.mjs} +1 -1
  13. package/dist/{chunk-TK5KEGNW.mjs → chunk-XXZZMG6Q.mjs} +15 -3
  14. package/dist/cli.js +20962 -7343
  15. package/dist/cli.mjs +1159 -245
  16. package/dist/credential-mcp-server.js +671 -127
  17. package/dist/credential-mcp-server.mjs +3 -3
  18. package/dist/discovery-mcp-server.js +710 -129
  19. package/dist/discovery-mcp-server.mjs +4 -4
  20. package/dist/dist-BXHLIG5H.mjs +17 -0
  21. package/dist/inbox-mcp-server.js +664 -123
  22. package/dist/inbox-mcp-server.mjs +3 -3
  23. package/dist/route-seed-worker.js +671 -124
  24. package/dist/route-seed-worker.mjs +3 -3
  25. package/dist/run-write-guard-preload.js +713 -125
  26. package/dist/run-write-guard-preload.mjs +4 -4
  27. package/dist/run-write-guard.js +713 -125
  28. package/dist/run-write-guard.mjs +3 -3
  29. package/dist/tar-extract-child.js +38 -14
  30. package/dist/tar-extract-child.mjs +2 -2
  31. package/package.json +3 -2
  32. package/dist/dist-2G7YTNH5.mjs +0 -17
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  dist_exports,
3
3
  init_dist
4
- } from "./chunk-OTKJZIOD.mjs";
4
+ } from "./chunk-EKY6CN4Y.mjs";
5
5
  import {
6
6
  __commonJS,
7
7
  __esm,
@@ -68077,7 +68077,13 @@ await expect(page).not.toHaveURL(/accounts\\.google\\.com/, { timeout: 60_000 })
68077
68077
  \`\`\`
68078
68078
  Never emit code that clicks, fills or asserts anything on accounts.google.com.`;
68079
68079
  }
68080
- function buildGoogleSignInTestGenSection(_opts) {
68080
+ function buildGoogleSignInTestGenSection(_opts, ctx = {}) {
68081
+ if (ctx.appLoginGiven) {
68082
+ return `
68083
+ ## SIGN-IN: USE THE TEST ACCOUNT
68084
+ This project gave a test account (the Browser Credentials / \`process.env.TEST_USER_*\` values). Sign in with it on the app's own email (or username) and password form, even when the app also offers Continue with Google. Do not click Continue with Google / Sign in with Google / Sign up with Google to sign in.
68085
+ Use Continue with Google only when this scenario's goal is the Google sign-in itself, or the app has no email/username and password sign-in at all. Then click that control on the app and wait until the browser is back on the app: \`await expect(page).not.toHaveURL(/accounts\\.google\\.com/, { timeout: 60_000 })\` (the runner completes Google's step). Never click, fill or assert anything on accounts.google.com, never type a password or code there.`;
68086
+ }
68081
68087
  return `
68082
68088
  ## GOOGLE SIGN-IN (the runner completes Google's step)
68083
68089
  When this scenario uses Continue with Google / Sign in with Google / Sign up with Google:
@@ -6225,7 +6225,7 @@ var require_repo_acquire = __commonJS({
6225
6225
  return mod && mod.__esModule ? mod : { "default": mod };
6226
6226
  };
6227
6227
  Object.defineProperty(exports, "__esModule", { value: true });
6228
- exports.ARCHIVE_TOO_LARGE_RE = exports.MAX_ZIP_TOTAL_BYTES = exports.MAX_ZIP_FILE_BYTES = exports.MAX_ZIP_ENTRIES = exports.MAX_ARCHIVE_BYTES = void 0;
6228
+ exports.ARCHIVE_TOO_LARGE_RE = exports.REPO_TARBALL_LIMITS = exports.MAX_ZIP_TOTAL_BYTES = exports.MAX_ZIP_FILE_BYTES = exports.MAX_ZIP_ENTRIES = exports.MAX_ARCHIVE_BYTES = void 0;
6229
6229
  exports.acquireRepoForImport = acquireRepoForImport;
6230
6230
  exports.downloadArchiveCapped = downloadArchiveCapped;
6231
6231
  exports.formatArchiveCap = formatArchiveCap;
@@ -6251,6 +6251,7 @@ var require_repo_acquire = __commonJS({
6251
6251
  exports.MAX_ZIP_ENTRIES = 2e4;
6252
6252
  exports.MAX_ZIP_FILE_BYTES = 50 * 1024 * 1024;
6253
6253
  exports.MAX_ZIP_TOTAL_BYTES = 600 * 1024 * 1024;
6254
+ exports.REPO_TARBALL_LIMITS = { maxEntries: 2e5, maxFileBytes: exports.MAX_ZIP_FILE_BYTES, maxTotalBytes: exports.MAX_ZIP_TOTAL_BYTES, oversizeFiles: "skip" };
6254
6255
  function envGithubTokenForImports() {
6255
6256
  const optedIn = ["1", "true", "yes", "on"].includes(String(process.env.RUNNER_IMPORT_ENV_GITHUB_TOKEN ?? "").trim().toLowerCase());
6256
6257
  if (!optedIn)
@@ -6294,7 +6295,14 @@ var require_repo_acquire = __commonJS({
6294
6295
  extractRoot,
6295
6296
  /* strip */
6296
6297
  1,
6297
- { subject: "repository archive", onUnavailable: "in-process", log, signal: spec.signal, tenantKey: spec.projectId }
6298
+ {
6299
+ subject: "repository archive",
6300
+ onUnavailable: "in-process",
6301
+ log,
6302
+ signal: spec.signal,
6303
+ tenantKey: spec.projectId,
6304
+ limits: { ...exports.REPO_TARBALL_LIMITS }
6305
+ }
6298
6306
  );
6299
6307
  await (0, promises_1.rm)(tarPath, { force: true }).catch(noop);
6300
6308
  log(`[repo-acquire] extracted github tarball (${archiveBytes} bytes)`);
@@ -6317,7 +6325,8 @@ var require_repo_acquire = __commonJS({
6317
6325
  log,
6318
6326
  signal: spec.signal,
6319
6327
  tenantKey: spec.projectId,
6320
- hint: "Check it is a valid .tar.gz, or upload it as a .zip."
6328
+ hint: "Check it is a valid .tar.gz, or upload it as a .zip.",
6329
+ limits: { ...exports.REPO_TARBALL_LIMITS }
6321
6330
  }
6322
6331
  );
6323
6332
  } else if (kind === "zip") {
@@ -6326,7 +6335,8 @@ var require_repo_acquire = __commonJS({
6326
6335
  log,
6327
6336
  signal: spec.signal,
6328
6337
  tenantKey: spec.projectId,
6329
- hint: "Check it is a valid .zip, or upload it as a .tar.gz."
6338
+ hint: "Check it is a valid .zip, or upload it as a .tar.gz.",
6339
+ oversizeFiles: exports.REPO_TARBALL_LIMITS.oversizeFiles
6330
6340
  });
6331
6341
  } else {
6332
6342
  await (0, promises_1.rm)(archivePath, { force: true }).catch(noop);
@@ -6536,7 +6546,7 @@ var require_repo_acquire = __commonJS({
6536
6546
  bad = new Error(`Tar entry path contains a NUL byte: ${JSON.stringify(shownPath)}`);
6537
6547
  } else if (typeof size !== "number" || !Number.isSafeInteger(size) || size < 0 || size > MAX_OCTAL_TAR_SIZE) {
6538
6548
  bad = new Error(`Tar entry has an invalid size field: ${shownPath}`);
6539
- } else if (size > limits.maxFileBytes) {
6549
+ } else if (size > limits.maxFileBytes && limits.oversizeFiles !== "skip") {
6540
6550
  bad = new Error(`Tar entry too large: ${shownPath} (declared ${size} bytes > ${limits.maxFileBytes} cap)`);
6541
6551
  } else if ((declaredTotal += size) > limits.maxTotalBytes) {
6542
6552
  bad = new Error(`Tar total uncompressed size exceeds ${limits.maxTotalBytes} cap (tar-bomb guard)`);
@@ -6603,7 +6613,8 @@ var require_repo_acquire = __commonJS({
6603
6613
  return false;
6604
6614
  }
6605
6615
  const sz = Number(entry.size ?? 0);
6606
- if (sz > limits.maxFileBytes) {
6616
+ const oversize = sz > limits.maxFileBytes;
6617
+ if (oversize && limits.oversizeFiles !== "skip") {
6607
6618
  capError = new Error(`Tar entry too large: ${p} (declared ${sz} bytes > ${limits.maxFileBytes} cap)`);
6608
6619
  return false;
6609
6620
  }
@@ -6612,13 +6623,13 @@ var require_repo_acquire = __commonJS({
6612
6623
  capError = new Error(`Tar total uncompressed size exceeds ${limits.maxTotalBytes} cap (tar-bomb guard)`);
6613
6624
  return false;
6614
6625
  }
6615
- return true;
6626
+ return !oversize;
6616
6627
  }
6617
6628
  }));
6618
6629
  if (capError)
6619
6630
  throw capError;
6620
6631
  }
6621
- async function extractZipSafely(buf, destDir, log = noop) {
6632
+ async function extractZipSafely(buf, destDir, log = noop, options = {}) {
6622
6633
  const zip = new adm_zip_1.default(buf);
6623
6634
  const entries = zip.getEntries();
6624
6635
  if (entries.length > exports.MAX_ZIP_ENTRIES) {
@@ -6627,6 +6638,7 @@ var require_repo_acquire = __commonJS({
6627
6638
  const destResolved = (0, node_path_1.resolve)(destDir);
6628
6639
  let total = 0;
6629
6640
  let files = 0;
6641
+ let skipped = 0;
6630
6642
  for (const entry of entries) {
6631
6643
  const name = entry.entryName;
6632
6644
  if (isUnsafeEntryName(name)) {
@@ -6641,12 +6653,18 @@ var require_repo_acquire = __commonJS({
6641
6653
  continue;
6642
6654
  }
6643
6655
  const declared = entry.header?.size ?? 0;
6644
- if (declared > exports.MAX_ZIP_FILE_BYTES) {
6645
- throw new Error(`Zip entry too large: ${name} (declared ${declared} bytes > ${exports.MAX_ZIP_FILE_BYTES} cap)`);
6646
- }
6647
6656
  if (total + declared > exports.MAX_ZIP_TOTAL_BYTES) {
6648
6657
  throw new Error(`Zip total uncompressed size exceeds ${exports.MAX_ZIP_TOTAL_BYTES} cap (zip-bomb guard)`);
6649
6658
  }
6659
+ if (declared > exports.MAX_ZIP_FILE_BYTES && options.oversizeFiles !== "skip") {
6660
+ throw new Error(`Zip entry too large: ${name} (declared ${declared} bytes > ${exports.MAX_ZIP_FILE_BYTES} cap)`);
6661
+ }
6662
+ if (declared > exports.MAX_ZIP_FILE_BYTES) {
6663
+ total += declared;
6664
+ skipped++;
6665
+ log(`[repo-acquire] zip: skipped ${name} (declared ${declared} bytes > ${exports.MAX_ZIP_FILE_BYTES} per-file cap)`);
6666
+ continue;
6667
+ }
6650
6668
  const data = entry.getData();
6651
6669
  if (data.length > exports.MAX_ZIP_FILE_BYTES) {
6652
6670
  throw new Error(`Zip entry too large: ${name} (${data.length} bytes > ${exports.MAX_ZIP_FILE_BYTES} cap)`);
@@ -6659,7 +6677,7 @@ var require_repo_acquire = __commonJS({
6659
6677
  await (0, promises_1.writeFile)(target, data);
6660
6678
  files++;
6661
6679
  }
6662
- log(`[repo-acquire] zip extracted: ${files} file(s), ${total} bytes`);
6680
+ log(`[repo-acquire] zip extracted: ${files} file(s), ${total} bytes${skipped ? `, ${skipped} oversize file(s) skipped` : ""}`);
6663
6681
  }
6664
6682
  async function normalizeRepoRoot(extractRoot) {
6665
6683
  let entries;
@@ -7162,7 +7180,13 @@ var require_tar_isolated = __commonJS({
7162
7180
  }
7163
7181
  async function extractZipIsolated(archivePath, destDir, options) {
7164
7182
  try {
7165
- await runTarJobIsolated({ op: "extract-zip", archivePath: (0, node_path_1.resolve)(archivePath), destDir: (0, node_path_1.resolve)(destDir) }, options.subject, options);
7183
+ const job = {
7184
+ op: "extract-zip",
7185
+ archivePath: (0, node_path_1.resolve)(archivePath),
7186
+ destDir: (0, node_path_1.resolve)(destDir),
7187
+ ...options.oversizeFiles ? { oversizeFiles: options.oversizeFiles } : {}
7188
+ };
7189
+ await runTarJobIsolated(job, options.subject, options);
7166
7190
  } catch (err) {
7167
7191
  if (err instanceof TarIsolationUnavailableError)
7168
7192
  throw refuseUnavailable(err, options.subject, options.log);
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  require_survey_network_guard
3
- } from "./chunk-36AWDKPL.mjs";
3
+ } from "./chunk-UZ67UBXH.mjs";
4
4
  import {
5
5
  dist_exports,
6
6
  init_dist
7
- } from "./chunk-OTKJZIOD.mjs";
7
+ } from "./chunk-EKY6CN4Y.mjs";
8
8
  import {
9
9
  __commonJS,
10
10
  __require,
@@ -462,6 +462,13 @@ var require_run_write_guard = __commonJS({
462
462
  function currentRunSignInSecrets() {
463
463
  return policyScope.getStore()?.signInSecrets ?? [];
464
464
  }
465
+ function isSocketIoPollingPost(pathname, search) {
466
+ if (pathname !== "/socket.io" && pathname !== "/socket.io/")
467
+ return false;
468
+ const eio = search.getAll("EIO");
469
+ const transport = search.getAll("transport");
470
+ return eio.length === 1 && /^[0-9]{1,2}$/.test(eio[0]) && transport.length === 1 && transport[0] === "polling";
471
+ }
465
472
  var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
466
473
  var MAX_BODY_CHARS = 262144;
467
474
  var MAX_SIGN_IN_LOG_LINES = 20;
@@ -1296,7 +1303,8 @@ var require_run_write_guard = __commonJS({
1296
1303
  const method = facts.method.toUpperCase();
1297
1304
  const host = url.hostname.toLowerCase().replace(/\.$/, "");
1298
1305
  const pathname = url.pathname;
1299
- if (READ_METHODS.has(method)) {
1306
+ const queryRead = method === "QUERY" && !(0, survey_network_guard_js_1.queryMethodMayWrite)(url, facts.postData, isGraphqlPath(pathname));
1307
+ if (READ_METHODS.has(method) || queryRead) {
1300
1308
  if (method === "GET" && isGraphqlPath(pathname) && (0, survey_network_guard_js_1.graphqlGetMayWrite)(url, facts.postData)) {
1301
1309
  return { action: "write", rule: "graphql-get-mutation", record: describe(facts, url, "graphql-get-mutation") };
1302
1310
  }
@@ -1351,6 +1359,8 @@ var require_run_write_guard = __commonJS({
1351
1359
  if (segs.length > 0 && SEARCH_LAST_SEGMENTS.has(segs[segs.length - 1]))
1352
1360
  return { action: "allow", rule: "search-post" };
1353
1361
  }
1362
+ if (appSite && method === "POST" && isSocketIoPollingPost(pathname, url.searchParams))
1363
+ return { action: "allow", rule: "realtime-poll" };
1354
1364
  if (hostIs(host, PAYMENT_HOSTS))
1355
1365
  return { action: "write", rule: "payment", record: describe(facts, url, "payment") };
1356
1366
  if (appSite)
@@ -2155,6 +2165,32 @@ var require_run_write_guard = __commonJS({
2155
2165
  return false;
2156
2166
  }
2157
2167
  }
2168
+ function contextStartsOffline(context) {
2169
+ try {
2170
+ return context._options?.offline === true;
2171
+ } catch {
2172
+ return false;
2173
+ }
2174
+ }
2175
+ function trackOffline(context) {
2176
+ const state = { offline: contextStartsOffline(context) };
2177
+ const original = context.setOffline;
2178
+ if (typeof original === "function") {
2179
+ Object.assign(context, {
2180
+ setOffline: async (offline) => {
2181
+ const before = state.offline;
2182
+ state.offline = offline === true;
2183
+ try {
2184
+ await original.call(context, offline);
2185
+ } catch (err) {
2186
+ state.offline = before;
2187
+ throw err;
2188
+ }
2189
+ }
2190
+ });
2191
+ }
2192
+ return state;
2193
+ }
2158
2194
  function hopRequestHeaders(pageHeaders, cookie) {
2159
2195
  const out = { accept: "*/*" };
2160
2196
  for (const [name, value] of Object.entries(pageHeaders)) {
@@ -2484,6 +2520,7 @@ var require_run_write_guard = __commonJS({
2484
2520
  return existing;
2485
2521
  const guard = new RunWriteGuard(policy, opts);
2486
2522
  countAppWebSocketMessages(context, guard);
2523
+ const emulated = policy.mode === "block" ? trackOffline(context) : { offline: false };
2487
2524
  if (policy.mode === "observe") {
2488
2525
  context.on("request", (request) => {
2489
2526
  try {
@@ -2516,6 +2553,11 @@ var require_run_write_guard = __commonJS({
2516
2553
  }
2517
2554
  }
2518
2555
  if (decision.action === "allow" && !READ_METHODS.has(request.method().toUpperCase()) && checksRedirect(decision.rule, request.url(), guard.policy)) {
2556
+ if (emulated.offline) {
2557
+ await route.fallback().catch(() => {
2558
+ });
2559
+ return;
2560
+ }
2519
2561
  await sendCheckingRedirect(route, guard, context);
2520
2562
  return;
2521
2563
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  require_test_inbox_broker,
3
3
  require_verification_inbox
4
- } from "./chunk-WVLBQDFP.mjs";
4
+ } from "./chunk-OXDU6ARE.mjs";
5
5
  import {
6
6
  __commonJS,
7
7
  __require
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  require_route_manifest
3
- } from "./chunk-TK5KEGNW.mjs";
3
+ } from "./chunk-XXZZMG6Q.mjs";
4
4
  import {
5
5
  require_survey_network_guard
6
- } from "./chunk-36AWDKPL.mjs";
6
+ } from "./chunk-UZ67UBXH.mjs";
7
7
  import {
8
8
  dist_exports,
9
9
  init_dist
10
- } from "./chunk-OTKJZIOD.mjs";
10
+ } from "./chunk-EKY6CN4Y.mjs";
11
11
  import {
12
12
  __commonJS,
13
13
  __toCommonJS
@@ -1904,8 +1904,9 @@ var require_repo_feature_verification = __commonJS({
1904
1904
  "the",
1905
1905
  "for"
1906
1906
  ]);
1907
+ var UNSPACED_SCRIPT = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
1907
1908
  function rootCardTerms(featureName) {
1908
- const words = String(featureName ?? "").toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 4 && !ROOT_GENERIC_WORDS.has(w));
1909
+ const words = String(featureName ?? "").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((w) => [...w].length >= (UNSPACED_SCRIPT.test(w) ? 2 : 4) && !ROOT_GENERIC_WORDS.has(w));
1909
1910
  return [...new Set(words.map((w) => w.length > 4 && w.endsWith("s") ? w.slice(0, -1) : w))];
1910
1911
  }
1911
1912
  function textShowsAnyTerm(text, terms) {
@@ -1959,7 +1960,7 @@ var require_repo_feature_verification = __commonJS({
1959
1960
  return null;
1960
1961
  }
1961
1962
  function normalizeControlText(text) {
1962
- return text.normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}\p{M}]+/gu, " ").trim();
1963
+ return text.normalize("NFKD").replace(/(\p{Script=Latin})\p{M}+/gu, "$1").normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}\p{M}]+/gu, " ").trim();
1963
1964
  }
1964
1965
  var SESSION_END_LABEL = /^(?:log|sign) ?(?:out|off)$/;
1965
1966
  var SESSION_END_SPELLINGS = ["log out", "logout", "log off", "logoff", "sign out", "signout", "sign off", "signoff"];
@@ -1972,13 +1973,35 @@ var require_repo_feature_verification = __commonJS({
1972
1973
  return [];
1973
1974
  return SESSION_END_LABEL.test(wanted) ? SESSION_END_SPELLINGS : [wanted];
1974
1975
  }
1976
+ var SHORT_LABEL_CHARS = 2;
1977
+ var WORD_SEGMENTER = new Intl.Segmenter(void 0, { granularity: "word" });
1978
+ function wholeWordsSpell(text, wanted) {
1979
+ const words = [];
1980
+ for (const s of WORD_SEGMENTER.segment(text))
1981
+ if (s.isWordLike)
1982
+ words.push(s.segment);
1983
+ for (let i = 0; i < words.length; i++) {
1984
+ let run = "";
1985
+ for (let j = i; j < words.length && run.length < wanted.length; j++) {
1986
+ run += words[j];
1987
+ if (run === wanted)
1988
+ return true;
1989
+ }
1990
+ }
1991
+ return false;
1992
+ }
1975
1993
  function textNamesControl(text, label) {
1976
1994
  const spellings = controlSpellings(label);
1977
1995
  if (spellings.length === 0)
1978
1996
  return false;
1979
1997
  const haystack = normalizeControlText(text);
1980
1998
  const squeezed = haystack.replace(/ /g, "");
1981
- return spellings.some((w) => haystack.includes(w) || squeezed.includes(w.replace(/ /g, "")));
1999
+ return spellings.some((w) => {
2000
+ const bare = w.replace(/ /g, "");
2001
+ if (!haystack.includes(w) && !squeezed.includes(bare))
2002
+ return false;
2003
+ return [...bare].length > SHORT_LABEL_CHARS || wholeWordsSpell(haystack, bare);
2004
+ });
1982
2005
  }
1983
2006
  function pageShowsControl(page, label) {
1984
2007
  const text = capturedPageText(page);
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  dist_exports,
3
3
  init_dist
4
- } from "./chunk-OTKJZIOD.mjs";
4
+ } from "./chunk-EKY6CN4Y.mjs";
5
5
  import {
6
6
  __commonJS,
7
7
  __require,
@@ -27,6 +27,7 @@ var require_survey_network_guard = __commonJS({
27
27
  exports.graphqlUrlCarriesQuery = graphqlUrlCarriesQuery;
28
28
  exports.graphqlUrlQueriesAreReads = graphqlUrlQueriesAreReads;
29
29
  exports.graphqlGetMayWrite = graphqlGetMayWrite;
30
+ exports.queryMethodMayWrite = queryMethodMayWrite;
30
31
  exports.writeShapedGet = writeShapedGet;
31
32
  exports.decideSurveyRequest = decideSurveyRequest;
32
33
  exports.installSurveyNetworkGuard = installSurveyNetworkGuard;
@@ -591,6 +592,12 @@ var require_survey_network_guard = __commonJS({
591
592
  const docs = url.searchParams.getAll("query");
592
593
  return docs.length > 0 && (typeof body === "string" && body.length > 0 || docs.some((doc) => !graphqlDocumentIsQueryOnly(doc)));
593
594
  }
595
+ function queryMethodMayWrite(url, body, graphqlEndpoint) {
596
+ if (graphqlEndpoint)
597
+ return true;
598
+ const operation = /\b(?:mutation|subscription)\b/i;
599
+ return typeof body === "string" && operation.test(body) || [...url.searchParams.values()].some((v) => operation.test(v));
600
+ }
594
601
  function isGraphqlGetWrite(url, s, body) {
595
602
  return isGraphqlEndpoint(url, s) && graphqlGetMayWrite(url, body);
596
603
  }
@@ -618,6 +625,7 @@ var require_survey_network_guard = __commonJS({
618
625
  "log-out"
619
626
  ]);
620
627
  exports.AMBIGUOUS_WRITE_VERBS = /* @__PURE__ */ new Set(["archive", "confirm", "disable"]);
628
+ var LABEL_PAGE_NOUNS = /* @__PURE__ */ new Set(["archive"]);
621
629
  var RESET_PAGE_WORDS = /* @__PURE__ */ new Set(["reset-password", "password-reset", "reset_password", "password_reset", "resetpassword", "forgot-password"]);
622
630
  var isTokenLikeSegment = (seg) => exports.ID_SEGMENT_RE.test(seg) || /^[A-Za-z0-9_-]{20,}$/.test(seg);
623
631
  var WRITE_SHAPED_STATIC_EXTENSIONS = [
@@ -763,6 +771,8 @@ var require_survey_network_guard = __commonJS({
763
771
  if (oneTimeToken)
764
772
  return { reason: "one-time-token" };
765
773
  const verb = opts.triggerLabel ? labelVerb(opts.triggerLabel) : void 0;
774
+ if (verb && LABEL_PAGE_NOUNS.has(verb) && last === verb && params.length === 0)
775
+ return null;
766
776
  return verb && !opts.ignoreVerbs?.has(verb) ? { reason: "label", verb } : null;
767
777
  }
768
778
  function writeShapedText(w) {
@@ -797,9 +807,10 @@ var require_survey_network_guard = __commonJS({
797
807
  return { action: "allow", rule: readMethod ? "read" : "non-http" };
798
808
  const appSite = isAppSite(url.hostname, s.appBaseUrl);
799
809
  const graphqlGet = method === "GET" && isGraphqlGetWrite(url, s, req.postData);
810
+ const queryRead = method === "QUERY" && !queryMethodMayWrite(url, req.postData, isGraphqlEndpoint(url, s));
800
811
  const inActionWindow = s.lastActionAt > 0 && s.now - s.lastActionAt <= ACTION_NAVIGATION_WINDOW_MS;
801
812
  const typedLoad = s.agentLoad === true && req.resourceType === "document";
802
- const writeShaped = method === "GET" && appSite && !graphqlGet && (inActionWindow || typedLoad) ? writeShapedGet(req.url, {
813
+ const writeShaped = (method === "GET" || queryRead) && appSite && !graphqlGet && (inActionWindow || typedLoad) ? writeShapedGet(req.url, {
803
814
  appSites: appSitesOf(s.appBaseUrl),
804
815
  resourceType: req.resourceType ?? "",
805
816
  // A label says what a click opens: it only judges a page load, never the app's own fetches, and never a typed URL.
@@ -807,7 +818,7 @@ var require_survey_network_guard = __commonJS({
807
818
  }) : null;
808
819
  if (writeShaped)
809
820
  return decideWriteShapedGet(req, s, writeShaped);
810
- if (readMethod && !graphqlGet)
821
+ if ((readMethod || queryRead) && !graphqlGet)
811
822
  return { action: "allow", rule: "read" };
812
823
  if (!appSite && ((0, shared_1.isAuthProviderHost)(url.hostname) || isTelemetryHost(url.hostname)))
813
824
  return { action: "allow", rule: "third-party" };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  require_route_manifest
3
- } from "./chunk-TK5KEGNW.mjs";
3
+ } from "./chunk-XXZZMG6Q.mjs";
4
4
  import {
5
5
  __commonJS,
6
6
  __require
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  dist_exports,
3
3
  init_dist
4
- } from "./chunk-OTKJZIOD.mjs";
4
+ } from "./chunk-EKY6CN4Y.mjs";
5
5
  import {
6
6
  __commonJS,
7
7
  __require,
@@ -133,6 +133,7 @@ var require_repo_brief = __commonJS({
133
133
  exports.briefSectionForFeature = briefSectionForFeature;
134
134
  exports.buildBriefRouteAllowlist = buildBriefRouteAllowlist;
135
135
  exports.buildBriefDoNotTestRoutes = buildBriefDoNotTestRoutes;
136
+ exports.briefJobRoute = briefJobRoute;
136
137
  exports.unopenedBriefJobs = unopenedBriefJobs;
137
138
  var shared_1 = (init_dist(), __toCommonJS(dist_exports));
138
139
  var model_json_document_js_1 = require_model_json_document();
@@ -391,14 +392,25 @@ ${scenarios}` : ""
391
392
  }
392
393
  return [...routes];
393
394
  }
394
- function unopenedBriefJobs(brief, coveredText) {
395
+ var ENTRY_ROUTE_RE = /(?:^|[\s(])((?:\/|#\/)[^\s?,;()]*)/;
396
+ function briefJobRoute(job) {
397
+ const match = ENTRY_ROUTE_RE.exec(job.entry.trim());
398
+ if (!match)
399
+ return null;
400
+ const route = match[1].replace(/[.:]+$/, "");
401
+ return route === "#/" ? null : route || null;
402
+ }
403
+ function unopenedBriefJobs(brief, coveredText, isOpenedRoute) {
395
404
  const haystack = coveredText.toLowerCase();
396
405
  return brief.jobs.filter((job) => {
397
406
  if (job.doNotTest)
398
407
  return false;
399
408
  const name = job.name.toLowerCase();
400
409
  const entry = job.entry.toLowerCase();
401
- return !haystack.includes(name) && !haystack.includes(entry);
410
+ if (haystack.includes(name) || haystack.includes(entry))
411
+ return false;
412
+ const route = isOpenedRoute ? briefJobRoute(job) : null;
413
+ return !(route && isOpenedRoute?.(route));
402
414
  });
403
415
  }
404
416
  exports.REPO_BRIEF_PROMPT = [