@validate.qa/runner 1.0.102 → 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-E2VEMBQQ.mjs → chunk-6N4QCCEH.mjs} +2 -2
  3. package/dist/{chunk-ZZYBRIBT.mjs → chunk-EBWZ7NGU.mjs} +11 -8
  4. package/dist/{chunk-3JM6XTSZ.mjs → chunk-EKY6CN4Y.mjs} +660 -123
  5. package/dist/{chunk-DN3O6EYT.mjs → chunk-MEE4DXRX.mjs} +19834 -3064
  6. package/dist/{chunk-CBVHPYVJ.mjs → chunk-OXDU6ARE.mjs} +86 -7
  7. package/dist/{chunk-AAUHXGDS.mjs → chunk-PUMRAFJS.mjs} +37 -13
  8. package/dist/{chunk-4WLP3RWN.mjs → chunk-TGVKABFN.mjs} +45 -3
  9. package/dist/{chunk-5DFBEL72.mjs → chunk-TS4OUJVT.mjs} +1 -1
  10. package/dist/{chunk-BXLEPT4M.mjs → chunk-UR7KDOTP.mjs} +32 -8
  11. package/dist/{chunk-E2RRAE27.mjs → chunk-UZ67UBXH.mjs} +14 -3
  12. package/dist/{chunk-HJ6FKHMM.mjs → chunk-VWE3SQ2Z.mjs} +1 -1
  13. package/dist/{chunk-PWFWMVZU.mjs → chunk-XXZZMG6Q.mjs} +15 -3
  14. package/dist/cli.js +35519 -17101
  15. package/dist/cli.mjs +1171 -251
  16. package/dist/credential-mcp-server.js +750 -133
  17. package/dist/credential-mcp-server.mjs +3 -3
  18. package/dist/discovery-mcp-server.js +714 -132
  19. package/dist/discovery-mcp-server.mjs +4 -4
  20. package/dist/dist-BXHLIG5H.mjs +17 -0
  21. package/dist/inbox-mcp-server.js +743 -129
  22. package/dist/inbox-mcp-server.mjs +3 -3
  23. package/dist/route-seed-worker.js +672 -125
  24. package/dist/route-seed-worker.mjs +3 -3
  25. package/dist/run-write-guard-preload.js +714 -126
  26. package/dist/run-write-guard-preload.mjs +4 -4
  27. package/dist/run-write-guard.js +714 -126
  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-52M5RDZ6.mjs +0 -17
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  dist_exports,
3
3
  init_dist
4
- } from "./chunk-3JM6XTSZ.mjs";
4
+ } from "./chunk-EKY6CN4Y.mjs";
5
5
  import {
6
6
  __commonJS,
7
7
  __esm,
@@ -67467,6 +67467,19 @@ var require_harness_init_script = __commonJS({
67467
67467
  const finishTransitions = opts.finishTransitions ?? (0, shared_1.parseBoolFlag)(process.env.HARNESS_FINISH_CSS_TRANSITIONS);
67468
67468
  const css = finishTransitions ? "*, *::before, *::after { animation-duration: 0s !important; animation-delay: 0s !important; }" : "*, *::before, *::after { transition-duration: 0s !important; animation-duration: 0s !important; transition-delay: 0s !important; animation-delay: 0s !important; }";
67469
67469
  return withHarnessSourceUrl(`(() => {
67470
+ // An error the browser throws for the page's own value, passed through one of the wrappers below (a bad
67471
+ // adoptedStyleSheets list, animate timing it refuses), is the app's: stock Playwright reports it as a page error.
67472
+ // Thrown from our wrapper, its top frame would be this script, and the console evidence drops such an error as
67473
+ // the runner's own. Our frames come out of its stack, so the app's call is the top frame, as without the harness.
67474
+ // (Under Error.stackTraceLimit the stack then has one or two frames fewer than stock's; the top frame is right.)
67475
+ const own = ${JSON.stringify(exports.HARNESS_INIT_SOURCE_URL)};
67476
+ const appError = (e) => {
67477
+ try {
67478
+ const stack = e !== null && typeof e === 'object' ? e.stack : undefined;
67479
+ if (typeof stack === 'string' && stack.includes(own)) e.stack = stack.split('\\n').filter((line) => !line.includes(own)).join('\\n');
67480
+ } catch (x) {}
67481
+ return e;
67482
+ };
67470
67483
  // Layer 1: CSS transitions and animations - instant, not removed
67471
67484
  // (duration 0s preserves final state; 'none' would prevent keyframes from completing)
67472
67485
  // The rule goes in a constructed stylesheet on document.adoptedStyleSheets, which adds no node to the DOM:
@@ -67480,6 +67493,31 @@ var require_harness_init_script = __commonJS({
67480
67493
  sheet.replaceSync(${JSON.stringify(css)});
67481
67494
  document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
67482
67495
  adopted = true;
67496
+ // A page that assigns the list without spreading it (document.adoptedStyleSheets = [mine]) would drop the
67497
+ // harness sheet and bring animations back: keep it, last, on every assignment. A bad value (not iterable,
67498
+ // or a list the browser refuses) goes to the browser, which throws its own error as the app's (appError).
67499
+ try {
67500
+ const desc = Object.getOwnPropertyDescriptor(Document.prototype, 'adoptedStyleSheets');
67501
+ if (desc && desc.get && desc.set) {
67502
+ Object.defineProperty(document, 'adoptedStyleSheets', {
67503
+ configurable: true,
67504
+ enumerable: desc.enumerable,
67505
+ get() { return desc.get.call(document); },
67506
+ set(value) {
67507
+ try {
67508
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function') || typeof value[Symbol.iterator] !== 'function') {
67509
+ desc.set.call(document, value);
67510
+ return;
67511
+ }
67512
+ const list = [...value];
67513
+ desc.set.call(document, list.includes(sheet) ? list : [...list, sheet]);
67514
+ } catch (e) {
67515
+ throw appError(e);
67516
+ }
67517
+ },
67518
+ });
67519
+ }
67520
+ } catch (e) {}
67483
67521
  }
67484
67522
  } catch (e) {}
67485
67523
  if (!adopted) try {
@@ -67556,16 +67594,51 @@ ${finishTransitions ? `
67556
67594
  ` : ""}
67557
67595
  // Layer 2: Web Animations API - make instant instead of no-op
67558
67596
  // (preserves animation completion callbacks that app code may depend on)
67597
+ // Timing the browser refuses (a negative, NaN or non-'auto' duration, a delay that is not finite) goes to the
67598
+ // browser as the page wrote it: zeroed, it raised no TypeError, so a page error stock Playwright reports was hidden.
67599
+ // Options that are not a plain object (an array, a class instance) keep what they inherit: the browser reads
67600
+ // every timing member through the prototype chain, and an array's inherited fill() is a TypeError. A copy kept
67601
+ // only own properties, so the error was hidden; a proxy overrides only duration and delay.
67559
67602
  try {
67560
67603
  const origAnimate = Element.prototype.animate;
67604
+ const refused = (options) => {
67605
+ try {
67606
+ if (options === undefined || options === null) return false;
67607
+ if (typeof options !== 'object' && typeof options !== 'function') return !(Number(options) >= 0);
67608
+ const duration = options.duration;
67609
+ if (duration !== undefined && duration !== 'auto' && !(typeof duration === 'number' && duration >= 0)) return true;
67610
+ const delay = options.delay;
67611
+ return delay !== undefined && !Number.isFinite(Number(delay));
67612
+ } catch (e) {
67613
+ return true;
67614
+ }
67615
+ };
67616
+ const instant = (options) => {
67617
+ try {
67618
+ // Plain: its prototype is null or an Object.prototype (of any frame's realm), so it inherits no timing.
67619
+ const proto = Object.getPrototypeOf(options);
67620
+ if (proto !== null && Object.getPrototypeOf(proto) !== null && typeof Proxy === 'function') {
67621
+ // The proxy's target is an empty object, not the options: a target with a read-only, non-configurable
67622
+ // duration or delay (a frozen instance, Object.create with a descriptor) would make returning 0 a proxy
67623
+ // invariant TypeError. Every other member is read from the options, the options as the receiver.
67624
+ return new Proxy({}, { get: (_target, key) => (key === 'duration' || key === 'delay' ? 0 : Reflect.get(options, key)) });
67625
+ }
67626
+ } catch (e) {}
67627
+ return Object.assign({}, options, { duration: 0, delay: 0 });
67628
+ };
67561
67629
  if (typeof origAnimate === 'function') {
67562
67630
  Element.prototype.animate = function (keyframes, options) {
67563
- if (typeof options === 'object' && options !== null) {
67564
- options = Object.assign({}, options, { duration: 0, delay: 0 });
67565
- } else {
67566
- options = 0; // covers number, undefined, and any other type
67631
+ try {
67632
+ if (refused(options)) return origAnimate.call(this, keyframes, options);
67633
+ if (typeof options === 'object' && options !== null) {
67634
+ options = instant(options);
67635
+ } else {
67636
+ options = 0; // covers number, undefined, and any other type
67637
+ }
67638
+ return origAnimate.call(this, keyframes, options);
67639
+ } catch (e) {
67640
+ throw appError(e);
67567
67641
  }
67568
- return origAnimate.call(this, keyframes, options);
67569
67642
  };
67570
67643
  }
67571
67644
  } catch (e) {}
@@ -68004,7 +68077,13 @@ await expect(page).not.toHaveURL(/accounts\\.google\\.com/, { timeout: 60_000 })
68004
68077
  \`\`\`
68005
68078
  Never emit code that clicks, fills or asserts anything on accounts.google.com.`;
68006
68079
  }
68007
- 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
+ }
68008
68087
  return `
68009
68088
  ## GOOGLE SIGN-IN (the runner completes Google's step)
68010
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-E2RRAE27.mjs";
3
+ } from "./chunk-UZ67UBXH.mjs";
4
4
  import {
5
5
  dist_exports,
6
6
  init_dist
7
- } from "./chunk-3JM6XTSZ.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-CBVHPYVJ.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-PWFWMVZU.mjs";
3
+ } from "./chunk-XXZZMG6Q.mjs";
4
4
  import {
5
5
  require_survey_network_guard
6
- } from "./chunk-E2RRAE27.mjs";
6
+ } from "./chunk-UZ67UBXH.mjs";
7
7
  import {
8
8
  dist_exports,
9
9
  init_dist
10
- } from "./chunk-3JM6XTSZ.mjs";
10
+ } from "./chunk-EKY6CN4Y.mjs";
11
11
  import {
12
12
  __commonJS,
13
13
  __toCommonJS
@@ -159,9 +159,10 @@ var require_exploration_parser = __commonJS({
159
159
  return false;
160
160
  return RECORD_TITLE_WITH_COMPANY.test(name);
161
161
  }
162
- var TYPED_VALUE_CALL_RE = /\.(fill|type|pressSequentially|insertText)\(\s*(['"])(?:\\.|(?!\2)[^\\])*\2(\s*,\s*\{[^{}]*\})?\s*\)/g;
162
+ var TYPED_VALUE_CALL_RE = /\.(fill|type|pressSequentially|insertText)\(\s*(['"`])(?:\\.|(?!\2)[^\\])*\2(\s*,\s*\{[^{}]*\})?\s*\)/g;
163
+ var TWO_ARG_TYPED_VALUE_CALL_RE = /\.(fill|type)\(\s*((?:(['"`])(?:\\.|(?!\3)[^\\])*\3|[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*))\s*,\s*(['"`])(?:\\.|(?!\4)[^\\])*\4(\s*,\s*\{[^{}]*\})?\s*\)/g;
163
164
  function maskTypedValues(codeLine, mask) {
164
- return codeLine.replace(TYPED_VALUE_CALL_RE, (_all, method, _quote, options) => `.${method}(${mask}${options ?? ""})`);
165
+ return codeLine.replace(TWO_ARG_TYPED_VALUE_CALL_RE, (_all, method, selector, _selQuote, _quote, options) => `.${method}(${selector}, ${mask}${options ?? ""})`).replace(TYPED_VALUE_CALL_RE, (_all, method, _quote, options) => `.${method}(${mask}${options ?? ""})`);
165
166
  }
166
167
  function sanitizePromptCodeLine(codeLine) {
167
168
  let sanitized = codeLine;
@@ -1903,8 +1904,9 @@ var require_repo_feature_verification = __commonJS({
1903
1904
  "the",
1904
1905
  "for"
1905
1906
  ]);
1907
+ var UNSPACED_SCRIPT = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
1906
1908
  function rootCardTerms(featureName) {
1907
- 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));
1908
1910
  return [...new Set(words.map((w) => w.length > 4 && w.endsWith("s") ? w.slice(0, -1) : w))];
1909
1911
  }
1910
1912
  function textShowsAnyTerm(text, terms) {
@@ -1958,7 +1960,7 @@ var require_repo_feature_verification = __commonJS({
1958
1960
  return null;
1959
1961
  }
1960
1962
  function normalizeControlText(text) {
1961
- return text.toLowerCase().replace(/[^a-z0-9]+/g, " ").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();
1962
1964
  }
1963
1965
  var SESSION_END_LABEL = /^(?:log|sign) ?(?:out|off)$/;
1964
1966
  var SESSION_END_SPELLINGS = ["log out", "logout", "log off", "logoff", "sign out", "signout", "sign off", "signoff"];
@@ -1971,13 +1973,35 @@ var require_repo_feature_verification = __commonJS({
1971
1973
  return [];
1972
1974
  return SESSION_END_LABEL.test(wanted) ? SESSION_END_SPELLINGS : [wanted];
1973
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
+ }
1974
1993
  function textNamesControl(text, label) {
1975
1994
  const spellings = controlSpellings(label);
1976
1995
  if (spellings.length === 0)
1977
1996
  return false;
1978
1997
  const haystack = normalizeControlText(text);
1979
1998
  const squeezed = haystack.replace(/ /g, "");
1980
- 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
+ });
1981
2005
  }
1982
2006
  function pageShowsControl(page, label) {
1983
2007
  const text = capturedPageText(page);
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  dist_exports,
3
3
  init_dist
4
- } from "./chunk-3JM6XTSZ.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-PWFWMVZU.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-3JM6XTSZ.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 = [