@validate.qa/runner 1.0.56 → 1.0.57

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 (3) hide show
  1. package/dist/cli.js +1232 -530
  2. package/dist/cli.mjs +1232 -530
  3. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -1772,7 +1772,12 @@ var init_constants = __esm({
1772
1772
  "VALIDATE_TASK_ARN",
1773
1773
  "VALIDATE_RUNNER_AUTH",
1774
1774
  "VALIDATE_SERVER_URL",
1775
- "VALIDATE_LIVE_BROWSER_STATE_PATH"
1775
+ "VALIDATE_LIVE_BROWSER_STATE_PATH",
1776
+ // Register-and-verify test identity (parent-minted per run; see runner-core
1777
+ // test-inbox-broker.ts). A customer variable must never spoof the address a
1778
+ // generated test registers with or the derived account password.
1779
+ "VQA_TEST_INBOX_EMAIL",
1780
+ "VQA_TEST_ACCOUNT_PASSWORD"
1776
1781
  ]);
1777
1782
  SPAWNED_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
1778
1783
  // Runtime essentials - Node.js and Playwright need these to function
@@ -3869,8 +3874,9 @@ function inlineLocatorBindings(stmts) {
3869
3874
  function stripLoginScaffolding(code) {
3870
3875
  const out = [];
3871
3876
  let inAuth = false;
3877
+ let inInbox = false;
3872
3878
  for (const ln of code.split("\n")) {
3873
- if (!inAuth && AUTH_START.test(ln)) {
3879
+ if (!inAuth && !inInbox && AUTH_START.test(ln)) {
3874
3880
  inAuth = true;
3875
3881
  continue;
3876
3882
  }
@@ -3879,6 +3885,15 @@ function stripLoginScaffolding(code) {
3879
3885
  inAuth = false;
3880
3886
  continue;
3881
3887
  }
3888
+ if (!inInbox && INBOX_START.test(ln)) {
3889
+ inInbox = true;
3890
+ continue;
3891
+ }
3892
+ if (inInbox) {
3893
+ if (INBOX_END.test(ln))
3894
+ inInbox = false;
3895
+ continue;
3896
+ }
3882
3897
  if (LOGIN_CALL_LINE.test(ln))
3883
3898
  continue;
3884
3899
  out.push(ln);
@@ -4267,7 +4282,7 @@ function validateAssertionStrength(code, opts = {}) {
4267
4282
  signals
4268
4283
  };
4269
4284
  }
4270
- var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD, LOCATOR_BINDING, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, HAS_TEXT_FILTER, OUTCOME_TEXT, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, NEGATED_ASSERT, TYPED_INPUT, TITLE_LINE, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES;
4285
+ var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD, LOCATOR_BINDING, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, HAS_TEXT_FILTER, OUTCOME_TEXT, 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, TYPED_INPUT, TITLE_LINE, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES;
4271
4286
  var init_assertion_strength = __esm({
4272
4287
  "../shared/dist/assertion-strength.js"() {
4273
4288
  "use strict";
@@ -4317,6 +4332,8 @@ var init_assertion_strength = __esm({
4317
4332
  AUTH_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_AUTH/;
4318
4333
  AUTH_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_AUTH/;
4319
4334
  LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*;?\s*$/;
4335
+ INBOX_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_INBOX/;
4336
+ INBOX_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_INBOX/;
4320
4337
  NEGATED_ASSERT = /\.not\.(toBeVisible|toContainText|toHaveText|toBeChecked)\b|\.toBeHidden\s*\(/;
4321
4338
  TYPED_INPUT = /\.(fill|type|pressSequentially|setInputFiles)\s*\(/;
4322
4339
  TITLE_LINE = /^\s*(?:test|describe)(?:\.\w+)?\s*\(/;
@@ -4359,10 +4376,7 @@ function isEnvironmentalError(run2) {
4359
4376
  return false;
4360
4377
  if (run2.status !== "ERROR")
4361
4378
  return false;
4362
- const err = run2.error;
4363
- if (typeof err !== "string")
4364
- return false;
4365
- return ENVIRONMENTAL_ERROR_PREFIXES.some((p) => err.startsWith(p));
4379
+ return classifyErrorMessage(run2.error) !== null;
4366
4380
  }
4367
4381
  function isCountedFailure(run2) {
4368
4382
  if (!run2)
@@ -4381,9 +4395,10 @@ function environmentalReason(run2) {
4381
4395
  function classifyErrorMessage(error2) {
4382
4396
  if (typeof error2 !== "string")
4383
4397
  return null;
4384
- if (error2.startsWith("RATE_LIMIT_BLOCKED:"))
4398
+ const normalized = error2.replace(/^\s+/, "").replace(/^Error:\s*/i, "");
4399
+ if (normalized.startsWith("RATE_LIMIT_BLOCKED:"))
4385
4400
  return "rate_limit";
4386
- if (error2.startsWith("CONFIG_ISSUE:"))
4401
+ if (normalized.startsWith("CONFIG_ISSUE:"))
4387
4402
  return "config_issue";
4388
4403
  return null;
4389
4404
  }
@@ -72605,7 +72620,7 @@ var require_verification_inbox = __commonJS({
72605
72620
  };
72606
72621
  })();
72607
72622
  Object.defineProperty(exports, "__esModule", { value: true });
72608
- exports.VerificationInboxReader = void 0;
72623
+ exports.VerificationInboxReader = exports.InboxAuthError = void 0;
72609
72624
  exports.isVerificationInboxConfigured = isVerificationInboxConfigured;
72610
72625
  exports.describeVerificationInboxConfigGap = describeVerificationInboxConfigGap;
72611
72626
  exports.loadVerificationInboxConfig = loadVerificationInboxConfig;
@@ -72617,8 +72632,10 @@ var require_verification_inbox = __commonJS({
72617
72632
  exports.applyPlusTag = applyPlusTag;
72618
72633
  exports.deriveRegistrationPassword = deriveRegistrationPassword;
72619
72634
  exports.extractVerificationCode = extractVerificationCode;
72635
+ exports.extractVerificationCodeDetailed = extractVerificationCodeDetailed;
72620
72636
  exports.extractVerificationLink = extractVerificationLink;
72621
72637
  exports.collectMessageUrls = collectMessageUrls;
72638
+ exports.isInboxAuthFailure = isInboxAuthFailure;
72622
72639
  var node_crypto_1 = __require("crypto");
72623
72640
  var DEFAULT_MAILBOX = "INBOX";
72624
72641
  var DEFAULT_GMAIL_SPAM_MAILBOX = "[Gmail]/Spam";
@@ -72791,6 +72808,9 @@ var require_verification_inbox = __commonJS({
72791
72808
  var CODE_TOKEN = new RegExp(`\\b(${CODE_ALT})\\b`, "gi");
72792
72809
  var YEAR_CONTEXT = /(©|\(c\)|copyright|all rights reserved|®)/i;
72793
72810
  function extractVerificationCode(text, html, subject) {
72811
+ return extractVerificationCodeDetailed(text, html, subject).code;
72812
+ }
72813
+ function extractVerificationCodeDetailed(text, html, subject) {
72794
72814
  const subj = collapseSpacedDigits(subject || "");
72795
72815
  const bodies = [text || "", stripHtml(html || "")].map(collapseSpacedDigits);
72796
72816
  const withSubject = [subj, ...bodies];
@@ -72799,7 +72819,7 @@ var require_verification_inbox = __commonJS({
72799
72819
  if (lone) {
72800
72820
  const c = cleanCode(lone[1]);
72801
72821
  if (c)
72802
- return c;
72822
+ return { code: c, confidence: "subject" };
72803
72823
  }
72804
72824
  }
72805
72825
  for (const body of withSubject) {
@@ -72811,26 +72831,28 @@ var require_verification_inbox = __commonJS({
72811
72831
  continue;
72812
72832
  const m = matchBestCode(line);
72813
72833
  if (m)
72814
- return m;
72834
+ return { code: m, confidence: "keyword" };
72815
72835
  }
72816
72836
  const windowed = body.match(new RegExp(`${CODE_KEYWORDS.source}[\\s\\S]{0,40}?\\b(${CODE_ALT})\\b`, "i"));
72817
72837
  if (windowed && windowed[1]) {
72818
72838
  const cleaned = cleanCode(windowed[1]);
72819
72839
  if (cleaned && !looksLikeYearOrDate(cleaned))
72820
- return cleaned;
72840
+ return { code: cleaned, confidence: "keyword" };
72821
72841
  }
72822
72842
  }
72843
+ const distinct = /* @__PURE__ */ new Set();
72823
72844
  for (const body of bodies) {
72824
72845
  if (!body)
72825
72846
  continue;
72826
- const candidates = body.match(/\b[0-9]{4,8}\b/g);
72827
- if (!candidates)
72828
- continue;
72829
- const real = candidates.find((n) => !looksLikeYearOrDate(n));
72830
- if (real)
72831
- return real;
72847
+ for (const n of body.match(/\b[0-9]{4,8}\b/g) ?? []) {
72848
+ if (!looksLikeYearOrDate(n))
72849
+ distinct.add(n);
72850
+ }
72832
72851
  }
72833
- return null;
72852
+ if (distinct.size === 1) {
72853
+ return { code: distinct.values().next().value ?? null, confidence: "fallback" };
72854
+ }
72855
+ return { code: null, confidence: null };
72834
72856
  }
72835
72857
  function matchBestCode(line) {
72836
72858
  const hasYearContext = YEAR_CONTEXT.test(line);
@@ -72952,6 +72974,28 @@ var require_verification_inbox = __commonJS({
72952
72974
  }
72953
72975
  var MAX_WAIT_CEILING_MS = 18e4;
72954
72976
  var MIN_POLL_MS = 2e3;
72977
+ var InboxAuthError = class extends Error {
72978
+ constructor(message) {
72979
+ super(message);
72980
+ this.name = "InboxAuthError";
72981
+ }
72982
+ };
72983
+ exports.InboxAuthError = InboxAuthError;
72984
+ function isInboxAuthFailure(error2) {
72985
+ if (!error2)
72986
+ return false;
72987
+ if (error2 instanceof InboxAuthError)
72988
+ return true;
72989
+ const e = error2;
72990
+ if (e.authenticationFailed === true)
72991
+ return true;
72992
+ const hay = `${e.responseText ?? ""} ${e.message ?? ""}`;
72993
+ return /\bAUTHENTICATIONFAILED\b|\bInvalid credentials\b/i.test(hay);
72994
+ }
72995
+ function isPlusTagged(address) {
72996
+ const at = address.indexOf("@");
72997
+ return at > 0 && address.slice(0, at).includes("+");
72998
+ }
72955
72999
  var VerificationInboxReader = class {
72956
73000
  config;
72957
73001
  client = null;
@@ -72981,7 +73025,14 @@ var require_verification_inbox = __commonJS({
72981
73025
  });
72982
73026
  client.on("error", () => {
72983
73027
  });
72984
- await client.connect();
73028
+ try {
73029
+ await client.connect();
73030
+ } catch (error2) {
73031
+ if (isInboxAuthFailure(error2)) {
73032
+ throw new InboxAuthError(`IMAP authentication rejected for ${this.config.user} \u2014 the inbox app password is revoked or wrong`);
73033
+ }
73034
+ throw error2;
73035
+ }
72985
73036
  this.client = client;
72986
73037
  return client;
72987
73038
  })();
@@ -73026,7 +73077,10 @@ var require_verification_inbox = __commonJS({
73026
73077
  } finally {
73027
73078
  lock.release();
73028
73079
  }
73029
- } catch {
73080
+ } catch (error2) {
73081
+ if (isInboxAuthFailure(error2)) {
73082
+ throw error2 instanceof InboxAuthError ? error2 : new InboxAuthError(`IMAP authentication rejected for ${this.config.user} \u2014 the inbox app password is revoked or wrong`);
73083
+ }
73030
73084
  return { uid: 0, uidValidity: 0 };
73031
73085
  }
73032
73086
  }
@@ -73077,23 +73131,52 @@ var require_verification_inbox = __commonJS({
73077
73131
  }
73078
73132
  }
73079
73133
  /**
73080
- * Poll until a matching message arrives or the deadline passes. Returns the
73081
- * newest matching message or null on timeout.
73134
+ * Poll until a message with a STRONG verification signal (subject/keyword
73135
+ * code or a positive-scoring link) arrives, or the deadline passes.
73136
+ *
73137
+ * Signal-less mail addressed to the same recipient (a welcome email, a
73138
+ * receipt) is remembered but polled PAST — returning it immediately used to
73139
+ * hand welcome-mail order numbers to the caller as "the code" whenever the
73140
+ * welcome email out-raced the verification email. Only at the deadline is
73141
+ * the newest signal-less message surfaced (better than pretending the
73142
+ * mailbox is empty: the caller can still read it and decide).
73082
73143
  */
73083
73144
  async waitForMessage(opts) {
73084
73145
  const maxWaitMs = Math.min(opts.maxWaitMs ?? 9e4, MAX_WAIT_CEILING_MS);
73085
73146
  const pollIntervalMs = Math.max(opts.pollIntervalMs ?? 5e3, MIN_POLL_MS);
73086
73147
  const deadline = startTime() + maxWaitMs;
73148
+ const limit = Math.max(1, Math.min(opts.limit ?? 10, 50));
73149
+ const satisfies = (m) => {
73150
+ if (opts.require === "code")
73151
+ return m.code !== null && m.codeConfidence !== "fallback";
73152
+ if (opts.require === "link")
73153
+ return m.link !== null;
73154
+ return m.hasVerificationSignal;
73155
+ };
73156
+ let weakest = null;
73157
+ const check2 = async () => {
73158
+ if (opts.stats)
73159
+ opts.stats.filteredOut = 0;
73160
+ const messages = await this.fetchMatching({ ...opts, limit }, true);
73161
+ if (!opts.require)
73162
+ return messages[0] ?? null;
73163
+ const strong = messages.find((m) => satisfies(m) && !m.senderMismatch) ?? messages.find(satisfies);
73164
+ if (strong)
73165
+ return strong;
73166
+ if (messages[0])
73167
+ weakest = messages[0];
73168
+ return null;
73169
+ };
73087
73170
  do {
73088
- const latest = await this.getLatest(opts);
73089
- if (latest)
73090
- return latest;
73171
+ const found = await check2();
73172
+ if (found)
73173
+ return found;
73091
73174
  const remaining = deadline - startTime();
73092
73175
  if (remaining <= 0)
73093
73176
  break;
73094
73177
  await sleep(Math.min(pollIntervalMs, remaining));
73095
73178
  } while (startTime() < deadline);
73096
- return this.getLatest(opts);
73179
+ return await check2() ?? weakest;
73097
73180
  }
73098
73181
  async fetchMatching(opts, withBody) {
73099
73182
  const limit = Math.max(1, Math.min(opts.limit ?? 10, 50));
@@ -73107,7 +73190,7 @@ var require_verification_inbox = __commonJS({
73107
73190
  throw e;
73108
73191
  }
73109
73192
  }
73110
- results.sort((a, b) => Date.parse(b.date) - Date.parse(a.date));
73193
+ results.sort((a, b) => Number(a.senderMismatch ?? false) - Number(b.senderMismatch ?? false) || Date.parse(b.date) - Date.parse(a.date));
73111
73194
  return results.slice(0, limit);
73112
73195
  }
73113
73196
  async fetchMatchingInMailbox(opts, withBody, mailbox, limit) {
@@ -73115,6 +73198,7 @@ var require_verification_inbox = __commonJS({
73115
73198
  const since = new Date(Math.max(0, opts.sinceMs));
73116
73199
  const lock = await client.getMailboxLock(mailbox);
73117
73200
  const results = [];
73201
+ const taggedRecipient = isPlusTagged(opts.toAddress);
73118
73202
  try {
73119
73203
  try {
73120
73204
  await client.noop();
@@ -73158,17 +73242,23 @@ var require_verification_inbox = __commonJS({
73158
73242
  const receivedMs = (parsed.date ?? msg.envelope?.date ?? /* @__PURE__ */ new Date(0)).getTime();
73159
73243
  if (!useUid && receivedMs < opts.sinceMs - 6e4)
73160
73244
  continue;
73161
- if (opts.fromMatch && !matchField(parsed.from?.text, opts.fromMatch))
73162
- continue;
73163
- if (opts.subjectMatch && !matchField(parsed.subject, opts.subjectMatch))
73245
+ const filterActive = Boolean(opts.fromMatch || opts.subjectMatch);
73246
+ const filterMatched = (!opts.fromMatch || matchField(parsed.from?.text, opts.fromMatch)) && (!opts.subjectMatch || matchField(parsed.subject, opts.subjectMatch));
73247
+ if (filterActive && !filterMatched && !taggedRecipient) {
73248
+ if (opts.stats)
73249
+ opts.stats.filteredOut += 1;
73164
73250
  continue;
73165
- results.push({ uid: msg.uid, parsed, receivedMs });
73251
+ }
73252
+ results.push({ uid: msg.uid, parsed, receivedMs, senderMismatch: filterActive && !filterMatched });
73166
73253
  }
73167
73254
  } finally {
73168
73255
  lock.release();
73169
73256
  }
73170
- results.sort((a, b) => b.receivedMs - a.receivedMs);
73171
- return results.slice(0, limit).map((r) => toMessage(messageId(mailbox, String(r.uid), this.config.mailbox), r.parsed, withBody, mailbox));
73257
+ results.sort((a, b) => Number(a.senderMismatch) - Number(b.senderMismatch) || b.receivedMs - a.receivedMs);
73258
+ return results.slice(0, limit).map((r) => ({
73259
+ ...toMessage(messageId(mailbox, String(r.uid), this.config.mailbox), r.parsed, withBody, mailbox),
73260
+ ...r.senderMismatch ? { senderMismatch: true } : {}
73261
+ }));
73172
73262
  }
73173
73263
  async close() {
73174
73264
  const client = this.client;
@@ -73199,12 +73289,18 @@ var require_verification_inbox = __commonJS({
73199
73289
  const text = parsed.text || "";
73200
73290
  const html = typeof parsed.html === "string" ? parsed.html : "";
73201
73291
  const summary = toSummary({ uid, parsed, mailbox });
73292
+ const { code, confidence } = extractVerificationCodeDetailed(text, html, parsed.subject || "");
73293
+ const link = extractVerificationLink(text, html);
73202
73294
  return {
73203
73295
  ...summary,
73204
73296
  text: withBody ? text : "",
73205
73297
  html: withBody ? html : "",
73206
- code: extractVerificationCode(text, html, parsed.subject || ""),
73207
- link: extractVerificationLink(text, html)
73298
+ code,
73299
+ link,
73300
+ codeConfidence: confidence,
73301
+ // A fallback-confidence code is NOT a signal: it is exactly the shape a
73302
+ // welcome/receipt email produces (one stray number, no keyword context).
73303
+ hasVerificationSignal: code !== null && confidence !== "fallback" || link !== null
73208
73304
  };
73209
73305
  }
73210
73306
  function toSummary(input) {
@@ -73402,7 +73498,11 @@ var require_inbox_tools = __commonJS({
73402
73498
  function err(message) {
73403
73499
  return { content: JSON.stringify({ error: message }), didMutate: false };
73404
73500
  }
73501
+ var INBOX_AUTH_DEAD_GUIDANCE = "the inbox authentication is PERMANENTLY rejected (revoked/wrong app password) \u2014 STOP calling inbox tools; none of them can succeed this run. Treat email verification as UNAVAILABLE: hard-stop at any emailed code/link wall, NEVER type a guessed code, and report verification-gated flows as BLOCKED (blockedReason: inbox authentication failed). Continue with publicly reachable coverage.";
73405
73502
  async function dispatchInboxTool(toolName, toolArgs, ctx) {
73503
+ if (ctx.state.authDead) {
73504
+ return err(`${toolName} skipped: ${INBOX_AUTH_DEAD_GUIDANCE}`);
73505
+ }
73406
73506
  try {
73407
73507
  switch (toolName) {
73408
73508
  case "provision_identity":
@@ -73424,6 +73524,10 @@ var require_inbox_tools = __commonJS({
73424
73524
  }
73425
73525
  } catch (e) {
73426
73526
  const message = e instanceof Error ? e.message : String(e);
73527
+ if ((0, verification_inbox_js_1.isInboxAuthFailure)(e)) {
73528
+ ctx.state.authDead = true;
73529
+ return err(`${toolName} failed: ${INBOX_AUTH_DEAD_GUIDANCE}`);
73530
+ }
73427
73531
  return err(`${toolName} failed: ${message}`);
73428
73532
  }
73429
73533
  }
@@ -73481,6 +73585,7 @@ var require_inbox_tools = __commonJS({
73481
73585
  return notReady;
73482
73586
  const maxWaitSeconds = clampNumber(args.maxWaitSeconds, 90, 5, 180);
73483
73587
  const { toAddress, altAddress } = inboxRecipients(ctx.state);
73588
+ const stats = { filteredOut: 0 };
73484
73589
  const msg = await ctx.reader.waitForMessage({
73485
73590
  toAddress,
73486
73591
  altAddress,
@@ -73489,13 +73594,16 @@ var require_inbox_tools = __commonJS({
73489
73594
  sinceUidValidity: ctx.state.sinceUidValidity,
73490
73595
  fromMatch: asStr(args.senderMatch),
73491
73596
  subjectMatch: asStr(args.subjectMatch),
73492
- maxWaitMs: maxWaitSeconds * 1e3
73597
+ maxWaitMs: maxWaitSeconds * 1e3,
73598
+ stats
73493
73599
  });
73494
73600
  if (!msg) {
73601
+ const filterHint = stats.filteredOut > 0 ? ` NOTE: ${stats.filteredOut} email(s) addressed to you WERE found but were excluded by your senderMatch/subjectMatch filter \u2014 transactional mail often comes from a delivery provider (mailgun/sendgrid/amazonses), so call wait_for_verification again WITHOUT senderMatch.` : "";
73495
73602
  return ok({
73496
73603
  found: false,
73497
73604
  waitedSeconds: maxWaitSeconds,
73498
- hint: 'No verification email yet \u2014 usually still in transit, not necessarily a failure. Call wait_for_verification AGAIN (safe to repeat; allow 30-90s total) and you may click an in-app "Resend" control AT MOST ONCE. Do NOT re-submit the signup form, do NOT create another account, and do not immediately assume your credentials are wrong \u2014 your account most likely already exists. BUT if you have already re-polled several times AND resent once and still nothing arrives, the signup may have genuinely failed: check the page for an error/validation message and report it (finish / needs-review) rather than re-polling forever \u2014 and still do not re-register. Use check_inbox to see what has arrived.'
73605
+ ...stats.filteredOut > 0 ? { filteredOut: stats.filteredOut } : {},
73606
+ hint: `No verification email yet \u2014 usually still in transit, not necessarily a failure.${filterHint} Call wait_for_verification AGAIN (safe to repeat; allow 30-90s total) and you may click an in-app "Resend" control AT MOST ONCE. Do NOT re-submit the signup form, do NOT create another account, and do not immediately assume your credentials are wrong \u2014 your account most likely already exists. BUT if you have already re-polled several times AND resent once and still nothing arrives, the signup may have genuinely failed: check the page for an error/validation message and report it (finish / needs-review) rather than re-polling forever \u2014 and still do not re-register. Use check_inbox to see what has arrived.`
73499
73607
  }, ctx.maxResultChars);
73500
73608
  }
73501
73609
  rememberLink(ctx.state, msg);
@@ -73507,6 +73615,8 @@ var require_inbox_tools = __commonJS({
73507
73615
  subject: msg.subject,
73508
73616
  from: msg.from,
73509
73617
  code: msg.code,
73618
+ ...msg.codeConfidence === "fallback" ? { codeConfidence: "low", codeNote: "This code was the only number in the email but had no verification keyword next to it \u2014 verify against read_verification_email if entering it fails." } : {},
73619
+ ...msg.senderMismatch ? { senderMismatch: true, senderNote: "This email is addressed to your exact registration address but its sender did not match your senderMatch \u2014 the recipient match is authoritative for your plus-tagged address." } : {},
73510
73620
  link,
73511
73621
  next: msg.code && link ? "Both a code and a confirmation link were found. Prefer typing the code into the verification field; if the app instead wants you to click, call open_verification_link with the link (and if the page it lands on then asks for the code, enter the code there)." : msg.code ? "Type this code into the verification field." : link ? "Call open_verification_link with this link to confirm. It navigates your current tab to the verify page; after it confirms, return to the app to keep exploring." : msg.link ? "A confirmation link was found but points at a private/internal host and was withheld for safety. Read the email body if you need to inspect it." : "No code/link auto-detected \u2014 call read_verification_email to read the body."
73512
73622
  }, ctx.maxResultChars);
@@ -73640,20 +73750,42 @@ var require_inbox_tools = __commonJS({
73640
73750
  return false;
73641
73751
  if (host === "::1" || host.startsWith("fe80:") || host.startsWith("fc") || host.startsWith("fd"))
73642
73752
  return false;
73643
- const m = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
73644
- if (m) {
73645
- const [a, b] = [Number(m[1]), Number(m[2])];
73646
- if (a === 127 || a === 0 || a === 10)
73647
- return false;
73648
- if (a === 169 && b === 254)
73649
- return false;
73650
- if (a === 192 && b === 168)
73753
+ if (host.includes(":")) {
73754
+ if (host === "::" || host === "::ffff:0:0")
73651
73755
  return false;
73652
- if (a === 172 && b >= 16 && b <= 31)
73653
- return false;
73654
- if (a >= 224)
73756
+ const dotted = host.match(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
73757
+ if (dotted && !ipv4IsPublic(dotted[1]))
73655
73758
  return false;
73759
+ const mapped = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
73760
+ if (mapped) {
73761
+ const hi = parseInt(mapped[1], 16);
73762
+ const lo = parseInt(mapped[2], 16);
73763
+ const ipv4 = `${hi >> 8 & 255}.${hi & 255}.${lo >> 8 & 255}.${lo & 255}`;
73764
+ if (!ipv4IsPublic(ipv4))
73765
+ return false;
73766
+ }
73656
73767
  }
73768
+ if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host) && !ipv4IsPublic(host))
73769
+ return false;
73770
+ return true;
73771
+ }
73772
+ function ipv4IsPublic(ipv4) {
73773
+ const m = ipv4.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
73774
+ if (!m)
73775
+ return true;
73776
+ const [a, b] = [Number(m[1]), Number(m[2])];
73777
+ if (a > 255 || b > 255 || Number(m[3]) > 255 || Number(m[4]) > 255)
73778
+ return false;
73779
+ if (a === 127 || a === 0 || a === 10)
73780
+ return false;
73781
+ if (a === 169 && b === 254)
73782
+ return false;
73783
+ if (a === 192 && b === 168)
73784
+ return false;
73785
+ if (a === 172 && b >= 16 && b <= 31)
73786
+ return false;
73787
+ if (a >= 224)
73788
+ return false;
73657
73789
  return true;
73658
73790
  }
73659
73791
  function clampNumber(v, dflt, min, max) {
@@ -85722,12 +85854,14 @@ var require_run_callback_proxy = __commonJS({
85722
85854
  }
85723
85855
  const runPath = `/api/runner/runs/${encodeURIComponent(input.runId)}`;
85724
85856
  const allowedPaths = /* @__PURE__ */ new Set([`${runPath}/credential`, `${runPath}/auth-state`]);
85857
+ const localPaths = new Map(Object.entries(input.localRoutes ?? {}).map(([suffix, handler]) => [`${runPath}/${suffix}`, handler]));
85725
85858
  const token = (0, node_crypto_1.randomBytes)(32).toString("base64url");
85726
85859
  const authHeader = `Bearer ${token}`;
85727
85860
  const server3 = (0, node_http_1.createServer)(async (req, res) => {
85728
85861
  try {
85729
85862
  const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
85730
- if (req.method !== "POST" || !allowedPaths.has(requestUrl.pathname) || requestUrl.search) {
85863
+ const localHandler = localPaths.get(requestUrl.pathname);
85864
+ if (req.method !== "POST" || !allowedPaths.has(requestUrl.pathname) && !localHandler || requestUrl.search) {
85731
85865
  sendJson(res, 404, { error: "Callback route not available" });
85732
85866
  return;
85733
85867
  }
@@ -85746,6 +85880,11 @@ var require_run_callback_proxy = __commonJS({
85746
85880
  sendJson(res, 400, { error: "Callback body must be a JSON object" });
85747
85881
  return;
85748
85882
  }
85883
+ if (localHandler) {
85884
+ const out = await localHandler(parsedBody);
85885
+ sendJson(res, out.status, out.body);
85886
+ return;
85887
+ }
85749
85888
  const upstreamBody = JSON.stringify({
85750
85889
  ...parsedBody,
85751
85890
  ...input.taskArn ? { taskArn: input.taskArn } : {},
@@ -85804,6 +85943,668 @@ var require_run_callback_proxy = __commonJS({
85804
85943
  }
85805
85944
  });
85806
85945
 
85946
+ // ../runner-core/dist/services/discovery-lint-flags.js
85947
+ var require_discovery_lint_flags = __commonJS({
85948
+ "../runner-core/dist/services/discovery-lint-flags.js"(exports) {
85949
+ "use strict";
85950
+ Object.defineProperty(exports, "__esModule", { value: true });
85951
+ exports.structuralLocatorLintMode = structuralLocatorLintMode;
85952
+ exports.isPwMethodPreflightEnabled = isPwMethodPreflightEnabled;
85953
+ exports.isVqaLoginDedupeEnabled = isVqaLoginDedupeEnabled;
85954
+ exports.isTestgenEmailVerifyEnabled = isTestgenEmailVerifyEnabled;
85955
+ function strictOn(value) {
85956
+ return value === "true" || value === "1";
85957
+ }
85958
+ function structuralLocatorLintMode() {
85959
+ const v = (process.env.DISCOVERY_STRUCTURAL_LOCATOR_LINT || "").trim().toLowerCase();
85960
+ if (v === "warn")
85961
+ return "warn";
85962
+ if (v === "true" || v === "1" || v === "enforce")
85963
+ return "enforce";
85964
+ return "off";
85965
+ }
85966
+ function isPwMethodPreflightEnabled() {
85967
+ return strictOn(process.env.DISCOVERY_PW_METHOD_PREFLIGHT);
85968
+ }
85969
+ function isVqaLoginDedupeEnabled() {
85970
+ return strictOn(process.env.DISCOVERY_VQALOGIN_DEDUPE);
85971
+ }
85972
+ function isTestgenEmailVerifyEnabled() {
85973
+ return strictOn(process.env.DISCOVERY_TESTGEN_EMAIL_VERIFY);
85974
+ }
85975
+ }
85976
+ });
85977
+
85978
+ // ../runner-core/dist/services/auth-recipe.js
85979
+ var require_auth_recipe = __commonJS({
85980
+ "../runner-core/dist/services/auth-recipe.js"(exports) {
85981
+ "use strict";
85982
+ Object.defineProperty(exports, "__esModule", { value: true });
85983
+ exports.RESERVED_RUNNER_ENV_VARS = exports.AUTH_MARKER_VERSION = void 0;
85984
+ exports.computeRecipeHash = computeRecipeHash;
85985
+ exports.buildAuthRecipe = buildAuthRecipe;
85986
+ exports.authProofLocator = authProofLocator;
85987
+ exports.urlProofPredicateExpr = urlProofPredicateExpr;
85988
+ exports.renderAuthProofAssertion = renderAuthProofAssertion;
85989
+ exports.renderProofBaselineCheck = renderProofBaselineCheck;
85990
+ exports.renderAuthFn = renderAuthFn;
85991
+ exports.isReservedRunnerEnvVar = isReservedRunnerEnvVar;
85992
+ exports.absoluteRoute = absoluteRoute;
85993
+ exports.renderReplayClickArrow = renderReplayClickArrow;
85994
+ exports.renderReplaySettleArrow = renderReplaySettleArrow;
85995
+ exports.renderAuthUrlWaitLine = renderAuthUrlWaitLine;
85996
+ exports.commandToReplayStep = commandToReplayStep;
85997
+ exports.buildReplayPlan = buildReplayPlan;
85998
+ exports.extractAuthRegion = extractAuthRegion;
85999
+ exports.stripAuthRegions = stripAuthRegions;
86000
+ exports.ensureExpectImported = ensureExpectImported;
86001
+ exports.insertAfterPwImport = insertAfterPwImport;
86002
+ exports.ensureLoginCall = ensureLoginCall;
86003
+ exports.upsertAuthBlock = upsertAuthBlock;
86004
+ exports.buildHealAuthStamp = buildHealAuthStamp;
86005
+ var crypto_1 = __require("crypto");
86006
+ var shared_1 = (init_dist(), __toCommonJS(dist_exports));
86007
+ var discovery_lint_flags_js_1 = require_discovery_lint_flags();
86008
+ exports.AUTH_MARKER_VERSION = "v3";
86009
+ var START_MARKER = `// >>> VALIDATE_QA_AUTH ${exports.AUTH_MARKER_VERSION} \u2014 generated, do not edit >>>`;
86010
+ var END_MARKER = `// <<< VALIDATE_QA_AUTH ${exports.AUTH_MARKER_VERSION} <<<`;
86011
+ var SHARED_AUTH_RUNTIME_ENABLED = `!/^false$/i.test((process.env.SHARED_AUTH_INJECT ?? '').trim())`;
86012
+ var AUTH_REGION_RE = /\/\/ >>> VALIDATE_QA_AUTH[^\n]*\r?\n[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH[^\n]*\r?\n?/g;
86013
+ function computeRecipeHash(input) {
86014
+ const canonical = JSON.stringify({
86015
+ preNavRoute: input.preNavRoute,
86016
+ preNavCmds: input.preNavCmds,
86017
+ route: input.route,
86018
+ cmds: input.cmds,
86019
+ redirectRoute: input.redirectRoute,
86020
+ authProof: input.authProof
86021
+ });
86022
+ return (0, crypto_1.createHash)("sha256").update(canonical).digest("hex").slice(0, 12);
86023
+ }
86024
+ function buildAuthRecipe(login, authProof, verifiedAt) {
86025
+ const preNavRoute = login.preNavRoute ?? "/";
86026
+ const preNavCmds = login.preNavCmds ?? [];
86027
+ const recipeHash = computeRecipeHash({
86028
+ preNavRoute,
86029
+ preNavCmds,
86030
+ route: login.route,
86031
+ cmds: login.cmds,
86032
+ redirectRoute: login.redirectRoute,
86033
+ authProof
86034
+ });
86035
+ return {
86036
+ version: 1,
86037
+ recipeHash,
86038
+ preNavRoute,
86039
+ preNavCmds,
86040
+ route: login.route,
86041
+ cmds: login.cmds,
86042
+ redirectRoute: login.redirectRoute,
86043
+ authProof,
86044
+ replayPlan: login.replayPlan ?? [],
86045
+ verifiedAt
86046
+ };
86047
+ }
86048
+ function authProofLocator(proof) {
86049
+ if (proof.kind === "url" || proof.kind === "endpoint") {
86050
+ throw new Error(`authProofLocator: ${proof.kind} proof has no element locator; callers must branch on proof.kind`);
86051
+ }
86052
+ if (proof.kind === "testid") {
86053
+ return `page.getByTestId(${JSON.stringify(proof.testId)})`;
86054
+ }
86055
+ if (proof.name) {
86056
+ return `page.getByRole(${JSON.stringify(proof.role)}, { name: ${JSON.stringify(proof.name)} })`;
86057
+ }
86058
+ return `page.getByRole(${JSON.stringify(proof.role)}).first()`;
86059
+ }
86060
+ function stripToPathname(route2) {
86061
+ return route2.replace(/[?#].*$/, "").replace(/\/+$/, "") || "/";
86062
+ }
86063
+ function urlProofPredicateExpr(route2, exact = false) {
86064
+ const p = stripToPathname(route2);
86065
+ if (p === "/")
86066
+ return `() => false`;
86067
+ const r = JSON.stringify(p);
86068
+ if (exact)
86069
+ return `(u) => u.pathname === ${r} || u.pathname === ${r} + '/'`;
86070
+ return `(u) => u.pathname === ${r} || u.pathname.startsWith(${r} + '/')`;
86071
+ }
86072
+ function renderStableExactUrlProof(pred, tabs) {
86073
+ return [
86074
+ `await page.waitForURL(${pred}, { timeout: 15000 });`,
86075
+ `if (await page.waitForURL((u) => !(${pred})(u), { timeout: 1500 }).then(() => true, () => false)) throw new Error('__vqa auth-proof: authenticated route was transient (rebounded to a logged-out route)');`,
86076
+ `if (await page.locator('input[type="password"]').first().isVisible().catch(() => false)) throw new Error('__vqa auth-proof: a login form is visible on the authenticated route \u2014 logged out');`
86077
+ ].join(`
86078
+ ${tabs}`);
86079
+ }
86080
+ var ENDPOINT_ORIGIN_EXPR = `(page.url().match(/^https?:\\/\\/[^/]+/) || [''])[0]`;
86081
+ function renderEndpointProbe(url, tabs, mode, originMode) {
86082
+ const u = JSON.stringify(url);
86083
+ const target = originMode === "pageurl" ? `${ENDPOINT_ORIGIN_EXPR} + ${u}` : u;
86084
+ const lines = [
86085
+ `const __vqaResp = await page.request.get(${target});`,
86086
+ `const __vqaJson = __vqaResp.ok() ? await __vqaResp.json().catch(() => null) : null;`,
86087
+ `const __vqaUser = __vqaJson && (__vqaJson.user ?? __vqaJson.data?.user ?? __vqaJson);`,
86088
+ `const __vqaAuthed = !!(__vqaUser && (__vqaUser.id || __vqaUser.email || __vqaUser.sub));`,
86089
+ mode === "authed" ? `if (!__vqaAuthed) throw new Error('__vqa auth-proof: identity endpoint shows no authenticated user');` : `if (__vqaAuthed) throw new Error('__vqa auth-baseline: identity endpoint shows an authenticated user while logged out');`
86090
+ ];
86091
+ return lines.join(`
86092
+ ${tabs}`);
86093
+ }
86094
+ function renderAuthProofAssertion(proof) {
86095
+ if (proof.kind === "endpoint") {
86096
+ return renderEndpointProbe(proof.url, " ", "authed", "baseurl");
86097
+ }
86098
+ if (proof.kind === "url") {
86099
+ const pred = urlProofPredicateExpr(proof.route, proof.exact);
86100
+ if (proof.exact)
86101
+ return renderStableExactUrlProof(pred, " ");
86102
+ return `await page.waitForURL(${pred}, { timeout: 15000 });`;
86103
+ }
86104
+ return `await expect(${authProofLocator(proof)}).toBeVisible({ timeout: 15000 });`;
86105
+ }
86106
+ var BASELINE_VISIBLE_TIMEOUT_MS = 15e3;
86107
+ function renderProofBaselineCheck(proof, tabs) {
86108
+ if (proof.kind === "endpoint") {
86109
+ return renderEndpointProbe(proof.url, tabs, "anon", "baseurl");
86110
+ }
86111
+ if (proof.kind === "url") {
86112
+ const pred = urlProofPredicateExpr(proof.route, proof.exact);
86113
+ return `if (await page.waitForURL(${pred}, { timeout: 2500 }).then(() => true, () => false)) throw new Error('__vqa auth-baseline: authed route reachable while logged out \u2014 not a discriminating proof');`;
86114
+ }
86115
+ const loc = authProofLocator(proof);
86116
+ return [
86117
+ `if (await ${loc}.waitFor({ state: 'visible', timeout: ${BASELINE_VISIBLE_TIMEOUT_MS} }).then(() => true, () => false)) {`,
86118
+ ` throw new Error('__vqa auth-baseline: authed-only proof visible while logged out \u2014 not a discriminating proof');`,
86119
+ `}`
86120
+ ].join(`
86121
+ ${tabs}`);
86122
+ }
86123
+ function normalizeCmd(cmd) {
86124
+ let c = cmd.trim();
86125
+ if (c.endsWith(";"))
86126
+ c = c.slice(0, -1);
86127
+ return ` ${c};`;
86128
+ }
86129
+ function renderAuthFn(recipe, opts) {
86130
+ const lines = [];
86131
+ lines.push(START_MARKER);
86132
+ lines.push(`async function __vqaLogin(page) {`);
86133
+ if (opts?.warmGuard) {
86134
+ const warmLanding = recipe.redirectRoute ?? (recipe.authProof.kind === "url" ? recipe.authProof.route : recipe.preNavRoute);
86135
+ const loginRoute = recipe.route ?? recipe.preNavRoute;
86136
+ lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED}) {`);
86137
+ lines.push(` await page.goto(${JSON.stringify(warmLanding)}, { waitUntil: 'domcontentloaded' }).catch(() => {});`);
86138
+ if (recipe.authProof.kind === "endpoint") {
86139
+ lines.push(` try {`);
86140
+ lines.push(` ${renderEndpointProbe(recipe.authProof.url, " ", "authed", "baseurl")}`);
86141
+ lines.push(` return;`);
86142
+ lines.push(` } catch {}`);
86143
+ } else if (recipe.authProof.kind === "url") {
86144
+ const pred = urlProofPredicateExpr(recipe.authProof.route, recipe.authProof.exact);
86145
+ lines.push(` await page.waitForURL((u) => !(${pred})(u), { timeout: 2000 }).catch(() => {});`);
86146
+ lines.push(` if ((${pred})(new URL(page.url())) && !(await page.locator('input[type="password"]').first().isVisible().catch(() => false))) return;`);
86147
+ } else if (loginRoute) {
86148
+ lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false) && new URL(page.url()).pathname !== new URL(${JSON.stringify(loginRoute)}, page.url()).pathname) return;`);
86149
+ } else {
86150
+ lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false)) return;`);
86151
+ }
86152
+ lines.push(` }`);
86153
+ }
86154
+ lines.push(` await page.goto(${JSON.stringify(recipe.preNavRoute)}, { waitUntil: 'domcontentloaded' });`);
86155
+ for (const cmd of recipe.preNavCmds)
86156
+ lines.push(normalizeCmd(cmd));
86157
+ if (recipe.preNavCmds.length === 0 && recipe.route && recipe.route !== recipe.preNavRoute) {
86158
+ lines.push(` await page.goto(${JSON.stringify(recipe.route)}, { waitUntil: 'domcontentloaded' });`);
86159
+ }
86160
+ for (const cmd of recipe.cmds)
86161
+ lines.push(normalizeCmd(cmd));
86162
+ lines.push(renderAuthUrlWaitLine(recipe.redirectRoute));
86163
+ lines.push(` ${renderAuthProofAssertion(recipe.authProof)}`);
86164
+ if (opts?.warmGuard) {
86165
+ lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED} && process.env.AUTH_FILE) await page.context().storageState({ path: process.env.AUTH_FILE });`);
86166
+ lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED} && process.env.VALIDATE_TASK_ARN && process.env.VALIDATE_CLAIM_EPOCH && process.env.VALIDATE_SERVER_URL && process.env.VALIDATE_RUN_ID) {`);
86167
+ lines.push(` try {`);
86168
+ lines.push(` const __vqaState = await page.context().storageState();`);
86169
+ lines.push(` await page.request.post(process.env.VALIDATE_SERVER_URL + '/api/runner/runs/' + process.env.VALIDATE_RUN_ID + '/auth-state', { headers: { Authorization: process.env.VALIDATE_RUNNER_AUTH || '' }, data: { storageState: JSON.stringify(__vqaState), taskArn: process.env.VALIDATE_TASK_ARN, claimEpoch: Number(process.env.VALIDATE_CLAIM_EPOCH) }, timeout: 10000 });`);
86170
+ lines.push(` } catch {}`);
86171
+ lines.push(` }`);
86172
+ }
86173
+ lines.push(`}`);
86174
+ lines.push(END_MARKER);
86175
+ return lines.join("\n");
86176
+ }
86177
+ exports.RESERVED_RUNNER_ENV_VARS = /* @__PURE__ */ new Set([
86178
+ "AUTH_FILE",
86179
+ "SHARED_AUTH_INJECT",
86180
+ "VALIDATE_RUN_ID",
86181
+ "VALIDATE_CLAIM_EPOCH",
86182
+ "VALIDATE_AUTH_CONFIG_HASH",
86183
+ "VALIDATE_TASK_ARN",
86184
+ "VALIDATE_RUNNER_AUTH",
86185
+ "VALIDATE_SERVER_URL",
86186
+ // Register-and-verify tests (VALIDATE_QA_INBOX region, inbox-stamp.ts): the
86187
+ // per-run minted registration address + its DERIVED throwaway-app password,
86188
+ // injected by the parent's test-inbox-broker — never project variables.
86189
+ "VQA_TEST_INBOX_EMAIL",
86190
+ "VQA_TEST_ACCOUNT_PASSWORD"
86191
+ ]);
86192
+ function isReservedRunnerEnvVar(key) {
86193
+ return exports.RESERVED_RUNNER_ENV_VARS.has(key);
86194
+ }
86195
+ function absoluteRoute(route2, baseUrl) {
86196
+ try {
86197
+ return new URL(route2, baseUrl).href;
86198
+ } catch {
86199
+ return route2;
86200
+ }
86201
+ }
86202
+ function renderReplayClickArrow(step) {
86203
+ const loc = step.locator.trim();
86204
+ if (step.kind === "wait") {
86205
+ return `async (page) => {
86206
+ await ${loc}.waitFor({ state: 'visible', timeout: 15000 });
86207
+ }`;
86208
+ }
86209
+ if (step.kind === "press") {
86210
+ return `async (page) => {
86211
+ await ${loc}.press(${JSON.stringify(step.key ?? "Enter")});
86212
+ }`;
86213
+ }
86214
+ return `async (page) => {
86215
+ await ${loc}.click();
86216
+ }`;
86217
+ }
86218
+ function renderReplaySettleArrow(proof) {
86219
+ if (proof.kind === "endpoint") {
86220
+ return `async (page) => {
86221
+ ${renderEndpointProbe(proof.url, " ", "authed", "pageurl")}
86222
+ }`;
86223
+ }
86224
+ if (proof.kind === "url") {
86225
+ const pred = urlProofPredicateExpr(proof.route, proof.exact);
86226
+ const body = proof.exact ? renderStableExactUrlProof(pred, " ") : `await page.waitForURL(${pred}, { timeout: 15000 });`;
86227
+ return `async (page) => {
86228
+ ${body}
86229
+ }`;
86230
+ }
86231
+ return `async (page) => {
86232
+ await ${authProofLocator(proof)}.waitFor({ state: 'visible', timeout: 15000 });
86233
+ }`;
86234
+ }
86235
+ function renderAuthUrlWaitLine(redirectRoute) {
86236
+ const opts = `{ waitUntil: 'domcontentloaded', timeout: 8000 }`;
86237
+ const tail = `.catch(() => { /* URL drift \u2014 the auth-proof assertion below is the gate */ });`;
86238
+ const rr = redirectRoute ? stripToPathname(redirectRoute) : null;
86239
+ if (rr && rr !== "/") {
86240
+ const r = JSON.stringify(rr);
86241
+ return ` await page.waitForURL((u) => u.pathname === ${r} || u.pathname.startsWith(${r} + '/'), ${opts})${tail}`;
86242
+ }
86243
+ if (rr === "/") {
86244
+ return ` await page.waitForURL((u) => !/\\/(login|sign-?in)/i.test(u.pathname), ${opts})${tail}`;
86245
+ }
86246
+ return ` await page.waitForURL((u) => u.pathname !== '/' && !/\\/(login|sign-?in)/i.test(u.pathname), ${opts})${tail}`;
86247
+ }
86248
+ function locatorChainOf(cmd) {
86249
+ const m = cmd.match(/\bpage\.[\s\S]*?(?=\.(?:fill|click|check|press|selectOption|setChecked|waitFor)\s*\()/);
86250
+ return m ? m[0].trim() : null;
86251
+ }
86252
+ function parseRoleName(locator) {
86253
+ const role = locator.match(/getByRole\(\s*['"`]([^'"`]+)['"`]/)?.[1];
86254
+ const name = locator.match(/getByRole\([^)]*name:\s*['"`]([^'"`]+)['"`]/)?.[1] ?? locator.match(/getByLabel\(\s*['"`]([^'"`]+)['"`]/)?.[1] ?? locator.match(/getByPlaceholder\(\s*['"`]([^'"`]+)['"`]/)?.[1];
86255
+ return { role, name };
86256
+ }
86257
+ function credentialKeyOf(cmd) {
86258
+ return cmd.match(/\.fill\(\s*process\.env\.([A-Za-z_$][\w$]*)!?\s*\)/)?.[1] ?? null;
86259
+ }
86260
+ function commandToReplayStep(cmd) {
86261
+ const chain = locatorChainOf(cmd);
86262
+ if (!chain)
86263
+ return null;
86264
+ const credKey = credentialKeyOf(cmd);
86265
+ if (cmd.includes(".fill(")) {
86266
+ if (!credKey)
86267
+ return null;
86268
+ const { role, name } = parseRoleName(chain);
86269
+ return { kind: "fill", credentialKey: credKey, locator: chain, role, name, element: name ?? role ?? "credential field" };
86270
+ }
86271
+ if (/\.waitFor\s*\(/.test(cmd))
86272
+ return { kind: "wait", locator: chain };
86273
+ const pressKey = cmd.match(/\.press\(\s*['"`]([^'"`]+)['"`]\s*\)/)?.[1];
86274
+ if (pressKey)
86275
+ return { kind: "press", locator: chain, key: pressKey };
86276
+ if (/\.(?:click|check|setChecked|selectOption)\s*\(/.test(cmd))
86277
+ return { kind: "click", locator: chain };
86278
+ return null;
86279
+ }
86280
+ function buildReplayPlan(login) {
86281
+ const plan = [];
86282
+ const preNavRoute = login.preNavRoute ?? "/";
86283
+ plan.push({ kind: "nav", route: preNavRoute });
86284
+ for (const c of login.preNavCmds ?? []) {
86285
+ const step = commandToReplayStep(c);
86286
+ if (step)
86287
+ plan.push(step);
86288
+ }
86289
+ if ((login.preNavCmds ?? []).length === 0 && login.route && login.route !== preNavRoute) {
86290
+ plan.push({ kind: "nav", route: login.route });
86291
+ }
86292
+ for (const c of login.cmds) {
86293
+ const step = commandToReplayStep(c);
86294
+ if (step)
86295
+ plan.push(step);
86296
+ }
86297
+ return plan;
86298
+ }
86299
+ function extractAuthRegion(code) {
86300
+ const m = code.match(new RegExp(AUTH_REGION_RE.source));
86301
+ return m ? m[0] : null;
86302
+ }
86303
+ function countAuthRegions(code) {
86304
+ const m = code.match(AUTH_REGION_RE);
86305
+ return m ? m.length : 0;
86306
+ }
86307
+ function stripAuthRegions(code) {
86308
+ return code.replace(AUTH_REGION_RE, "");
86309
+ }
86310
+ var PW_IMPORT_RE = /import\s*\{([^}]*)\}\s*from\s*['"]@playwright\/test['"]\s*;?/;
86311
+ function ensureExpectImported(code) {
86312
+ const m = code.match(PW_IMPORT_RE);
86313
+ if (!m)
86314
+ return code;
86315
+ const names = m[1].split(",").map((s) => s.trim()).filter(Boolean);
86316
+ if (names.includes("expect"))
86317
+ return code;
86318
+ const next = [...names, "expect"];
86319
+ return code.replace(PW_IMPORT_RE, `import { ${next.join(", ")} } from '@playwright/test';`);
86320
+ }
86321
+ function insertAfterPwImport(code, block) {
86322
+ const m = code.match(PW_IMPORT_RE);
86323
+ if (!m || m.index === void 0) {
86324
+ return `${block}
86325
+
86326
+ ${code.replace(/^\r?\n+/, "")}`;
86327
+ }
86328
+ const end = m.index + m[0].length;
86329
+ const head = code.slice(0, end);
86330
+ const tail = code.slice(end).replace(/^\r?\n+/, "");
86331
+ return `${head}
86332
+
86333
+ ${block}
86334
+
86335
+ ${tail}`;
86336
+ }
86337
+ var TEST_OPENER_RE = /test(?:\.(?:only|skip|fixme))?\s*\(\s*(['"`])(?:[^\\]|\\.)*?\1\s*,\s*(?:\{[\s\S]*?\}\s*,\s*)?async\s*\(\s*\{[^}]*\}[^)]*\)\s*=>\s*\{/;
86338
+ var LOGIN_CALL_RE = /await\s+__vqaLogin\s*\(\s*page\s*\)/;
86339
+ function ensureLoginCall(code) {
86340
+ if (code.match(new RegExp(TEST_OPENER_RE.source, "g")) === null)
86341
+ return code;
86342
+ let result = code;
86343
+ if ((0, discovery_lint_flags_js_1.isVqaLoginDedupeEnabled)()) {
86344
+ result = result.replace(/^[ \t]*await[ \t]+__vqaLogin[ \t]*\([ \t]*page[ \t]*\)[ \t]*;?[ \t]*\r?\n/gm, "");
86345
+ }
86346
+ const matches = [...result.matchAll(new RegExp(TEST_OPENER_RE.source, "g"))];
86347
+ for (let i = matches.length - 1; i >= 0; i--) {
86348
+ const m = matches[i];
86349
+ if (m.index === void 0)
86350
+ continue;
86351
+ const insertAt = m.index + m[0].length;
86352
+ const following = result.slice(insertAt, insertAt + 60);
86353
+ if (/^\s*await\s+__vqaLogin\s*\(\s*page\s*\)/.test(following))
86354
+ continue;
86355
+ result = `${result.slice(0, insertAt)}
86356
+ await __vqaLogin(page);${result.slice(insertAt)}`;
86357
+ }
86358
+ return result;
86359
+ }
86360
+ function upsertAuthBlock(code, ctx) {
86361
+ if (ctx.mode === "generate") {
86362
+ if (!ctx.recipe || !ctx.isAuthNeeded)
86363
+ return code;
86364
+ let result = stripAuthRegions(code);
86365
+ result = result.replace(/^[ \t]*await[ \t]+__vqaLogin[ \t]*\([ \t]*page[ \t]*\)[ \t]*;?[ \t]*\r?\n/gm, "");
86366
+ result = ensureExpectImported(result);
86367
+ const warmGuard = (0, shared_1.isSharedAuthInjectEnabled)();
86368
+ result = insertAfterPwImport(result, renderAuthFn(ctx.recipe, { warmGuard }));
86369
+ result = ensureLoginCall(result);
86370
+ const n = countAuthRegions(result);
86371
+ if (n !== 1) {
86372
+ throw new Error(`upsertAuthBlock(generate): expected exactly 1 auth region after stamp, got ${n}`);
86373
+ }
86374
+ if (!LOGIN_CALL_RE.test(result)) {
86375
+ throw new Error("upsertAuthBlock(generate): stamped __vqaLogin but found no test() opener to inject the call");
86376
+ }
86377
+ return result;
86378
+ }
86379
+ try {
86380
+ if (!ctx.preservedAuthRegion)
86381
+ return code;
86382
+ let result = ensureExpectImported(code);
86383
+ if (countAuthRegions(result) >= 1) {
86384
+ result = result.replace(AUTH_REGION_RE, "");
86385
+ result = insertAfterPwImport(result, ctx.preservedAuthRegion);
86386
+ } else {
86387
+ result = insertAfterPwImport(result, ctx.preservedAuthRegion);
86388
+ }
86389
+ result = ensureLoginCall(result);
86390
+ if (countAuthRegions(result) !== 1) {
86391
+ throw new Error("heal re-stamp produced non-unique auth region");
86392
+ }
86393
+ return result;
86394
+ } catch (err) {
86395
+ const reason = err instanceof Error ? err.message : String(err);
86396
+ ctx.onSignal?.({ type: "auth-stamp-degraded", reason });
86397
+ return code;
86398
+ }
86399
+ }
86400
+ function buildHealAuthStamp(originalCode, failureError, logs2) {
86401
+ const region = extractAuthRegion(originalCode);
86402
+ const loginFailed = !!failureError && /__vqaLogin/.test(failureError);
86403
+ return {
86404
+ mode: "heal",
86405
+ preservedAuthRegion: region && !loginFailed ? region : null,
86406
+ onSignal: logs2 ? (s) => logs2.push(`\u26A0 auth-stamp ${s.type}: ${s.reason}`) : void 0
86407
+ };
86408
+ }
86409
+ }
86410
+ });
86411
+
86412
+ // ../runner-core/dist/services/inbox-stamp.js
86413
+ var require_inbox_stamp = __commonJS({
86414
+ "../runner-core/dist/services/inbox-stamp.js"(exports) {
86415
+ "use strict";
86416
+ Object.defineProperty(exports, "__esModule", { value: true });
86417
+ exports.INBOX_HELPER_TOKEN_RE = exports.INBOX_HELPER_NAME = exports.INBOX_REGION_RE = exports.INBOX_MARKER_VERSION = void 0;
86418
+ exports.codeUsesInboxHelper = codeUsesInboxHelper;
86419
+ exports.stripInboxRegions = stripInboxRegions;
86420
+ exports.renderInboxFn = renderInboxFn;
86421
+ exports.upsertInboxBlock = upsertInboxBlock;
86422
+ var auth_recipe_js_1 = require_auth_recipe();
86423
+ exports.INBOX_MARKER_VERSION = "v1";
86424
+ var START_MARKER = `// >>> VALIDATE_QA_INBOX ${exports.INBOX_MARKER_VERSION} \u2014 generated, do not edit >>>`;
86425
+ var END_MARKER = `// <<< VALIDATE_QA_INBOX ${exports.INBOX_MARKER_VERSION} <<<`;
86426
+ exports.INBOX_REGION_RE = /\/\/ >>> VALIDATE_QA_INBOX[^\n]*\r?\n[\s\S]*?\/\/ <<< VALIDATE_QA_INBOX[^\n]*\r?\n?/g;
86427
+ exports.INBOX_HELPER_NAME = "__vqaWaitForEmail";
86428
+ exports.INBOX_HELPER_TOKEN_RE = /__vqaWaitForEmail\s*\(/;
86429
+ function codeUsesInboxHelper(code) {
86430
+ if (!code)
86431
+ return false;
86432
+ return exports.INBOX_HELPER_TOKEN_RE.test(code) || /VALIDATE_QA_INBOX/.test(code);
86433
+ }
86434
+ function stripInboxRegions(code) {
86435
+ return code.replace(exports.INBOX_REGION_RE, "");
86436
+ }
86437
+ function countInboxRegions(code) {
86438
+ const m = code.match(exports.INBOX_REGION_RE);
86439
+ return m ? m.length : 0;
86440
+ }
86441
+ function renderInboxFn() {
86442
+ const lines = [
86443
+ START_MARKER,
86444
+ `// Reads THIS run's verification email via the runner's inbox broker. The`,
86445
+ `// IMAP credential stays in the runner parent; this helper can only see mail`,
86446
+ `// addressed to the run's own registration address (VQA_TEST_INBOX_EMAIL).`,
86447
+ `// kind: 'code' \u2192 the one-time code, 'link' \u2192 the verification URL,`,
86448
+ `// 'either' / omitted \u2192 { code, link, subject, from } for flexible flows.`,
86449
+ `async function __vqaWaitForEmail(kind, opts) {`,
86450
+ ` const want = kind === 'code' || kind === 'link' ? kind : 'either';`,
86451
+ ` if (!process.env.VQA_TEST_INBOX_EMAIL || !process.env.VALIDATE_SERVER_URL || !process.env.VALIDATE_RUN_ID) {`,
86452
+ ` throw new Error('CONFIG_ISSUE: email verification unavailable - this runner has no verification inbox, so a register-and-verify test cannot run here');`,
86453
+ ` }`,
86454
+ ` const timeoutMs = Math.min(Math.max((opts && opts.timeoutMs) || 150000, 5000), 170000);`,
86455
+ ` const res = await fetch(process.env.VALIDATE_SERVER_URL + '/api/runner/runs/' + encodeURIComponent(process.env.VALIDATE_RUN_ID) + '/inbox-wait', {`,
86456
+ ` method: 'POST',`,
86457
+ ` headers: { authorization: process.env.VALIDATE_RUNNER_AUTH || '', 'content-type': 'application/json' },`,
86458
+ ` body: JSON.stringify({ kind: want, timeoutMs: timeoutMs }),`,
86459
+ ` });`,
86460
+ ` if (res.status === 404) {`,
86461
+ ` throw new Error('CONFIG_ISSUE: email verification unavailable - the runner inbox broker is not active for this run');`,
86462
+ ` }`,
86463
+ ` let data = null;`,
86464
+ ` try { data = await res.json(); } catch (e) { data = null; }`,
86465
+ ` if (!res.ok || !data) { throw new Error('inbox broker request failed (status ' + res.status + ')'); }`,
86466
+ ` if (!data.found) {`,
86467
+ ` if (data.error) { throw new Error('CONFIG_ISSUE: verification inbox failed on this runner - ' + data.error); }`,
86468
+ ` throw new Error('VERIFICATION_EMAIL_NOT_RECEIVED: no verification email arrived for this test account within ' + Math.round(timeoutMs / 1000) + 's - the app may not have sent it');`,
86469
+ ` }`,
86470
+ ` if (want === 'code' && !data.code) {`,
86471
+ ` throw new Error('VERIFICATION_EMAIL_HAS_NO_CODE: an email arrived (subject: ' + JSON.stringify(data.subject || '') + ') but no one-time code could be extracted');`,
86472
+ ` }`,
86473
+ ` if (want === 'link' && !data.link) {`,
86474
+ ` throw new Error('VERIFICATION_EMAIL_HAS_NO_LINK: an email arrived (subject: ' + JSON.stringify(data.subject || '') + ') but no verification link could be extracted');`,
86475
+ ` }`,
86476
+ ` return want === 'code' ? data.code : want === 'link' ? data.link : data;`,
86477
+ `}`,
86478
+ END_MARKER
86479
+ ];
86480
+ return lines.join("\n");
86481
+ }
86482
+ function upsertInboxBlock(code) {
86483
+ const stripped = stripInboxRegions(code);
86484
+ if (!exports.INBOX_HELPER_TOKEN_RE.test(stripped))
86485
+ return stripped;
86486
+ const result = (0, auth_recipe_js_1.insertAfterPwImport)(stripped, renderInboxFn());
86487
+ const n = countInboxRegions(result);
86488
+ if (n !== 1) {
86489
+ throw new Error(`upsertInboxBlock: expected exactly 1 inbox region after stamp, got ${n}`);
86490
+ }
86491
+ return result;
86492
+ }
86493
+ }
86494
+ });
86495
+
86496
+ // ../runner-core/dist/services/test-inbox-broker.js
86497
+ var require_test_inbox_broker = __commonJS({
86498
+ "../runner-core/dist/services/test-inbox-broker.js"(exports) {
86499
+ "use strict";
86500
+ Object.defineProperty(exports, "__esModule", { value: true });
86501
+ exports.INBOX_WAIT_ROUTE = exports.TEST_INBOX_PASSWORD_ENV = exports.TEST_INBOX_EMAIL_ENV = void 0;
86502
+ exports.deriveTestRunInboxTag = deriveTestRunInboxTag;
86503
+ exports.mintTestRunInboxIdentity = mintTestRunInboxIdentity;
86504
+ exports.decideInboxWaitResponse = decideInboxWaitResponse;
86505
+ exports.startTestInboxBroker = startTestInboxBroker;
86506
+ var node_crypto_1 = __require("crypto");
86507
+ var verification_inbox_js_1 = require_verification_inbox();
86508
+ var inbox_tools_js_1 = require_inbox_tools();
86509
+ exports.TEST_INBOX_EMAIL_ENV = "VQA_TEST_INBOX_EMAIL";
86510
+ exports.TEST_INBOX_PASSWORD_ENV = "VQA_TEST_ACCOUNT_PASSWORD";
86511
+ exports.INBOX_WAIT_ROUTE = "inbox-wait";
86512
+ var MIN_WAIT_MS = 5e3;
86513
+ var MAX_WAIT_MS = 17e4;
86514
+ var DEFAULT_WAIT_MS = 15e4;
86515
+ function deriveTestRunInboxTag(seed, projectId, runId) {
86516
+ const hostKey = (0, verification_inbox_js_1.seedToHostKey)(seed || "");
86517
+ const hash = (0, node_crypto_1.createHash)("sha256").update(`testrun|${hostKey}|${projectId ?? ""}|${runId}`).digest("hex").slice(0, 10);
86518
+ return `vqat${hash}`;
86519
+ }
86520
+ function mintTestRunInboxIdentity(config, input) {
86521
+ const tag = deriveTestRunInboxTag(input.baseUrl, input.projectId, input.runId);
86522
+ const email = (0, verification_inbox_js_1.applyPlusTag)(config.address, tag);
86523
+ return { email, password: (0, verification_inbox_js_1.deriveRegistrationPassword)(email), tag };
86524
+ }
86525
+ function decideInboxWaitResponse(msg, publicLink) {
86526
+ const code = msg && msg.code && msg.codeConfidence !== "fallback" ? msg.code : null;
86527
+ const link = publicLink;
86528
+ if (!msg || !code && !link)
86529
+ return { found: false };
86530
+ return { found: true, code, link, subject: msg.subject, from: msg.from };
86531
+ }
86532
+ async function startTestInboxBroker(opts) {
86533
+ const identity = mintTestRunInboxIdentity(opts.config, opts);
86534
+ const reader = new verification_inbox_js_1.VerificationInboxReader(opts.config);
86535
+ const sinceMs = Date.now();
86536
+ let sinceUid = 0;
86537
+ let sinceUidValidity = 0;
86538
+ let authDeadReason = null;
86539
+ try {
86540
+ const wm = await reader.getUidWatermark();
86541
+ sinceUid = wm.uid;
86542
+ sinceUidValidity = wm.uidValidity;
86543
+ } catch (error2) {
86544
+ if ((0, verification_inbox_js_1.isInboxAuthFailure)(error2)) {
86545
+ authDeadReason = error2 instanceof Error ? error2.message : String(error2);
86546
+ opts.log?.(`\u26A0 Inbox broker: IMAP authentication rejected at watermark probe \u2014 email waits will fail as CONFIG_ISSUE (${authDeadReason})`);
86547
+ } else {
86548
+ opts.log?.("\u26A0 Inbox broker: UID watermark unavailable, falling back to the time floor");
86549
+ }
86550
+ }
86551
+ opts.log?.(`\u{1F4EC} Inbox broker active for ${identity.email} (uid watermark ${sinceUid || "none"})`);
86552
+ let waiting = false;
86553
+ const handleWait = async (body) => {
86554
+ if (authDeadReason) {
86555
+ return { status: 200, body: { found: false, error: "inbox authentication failed (revoked/wrong app password)" } };
86556
+ }
86557
+ if (waiting) {
86558
+ return { status: 200, body: { found: false, error: "another verification wait is already in progress for this run" } };
86559
+ }
86560
+ waiting = true;
86561
+ const rawTimeout = typeof body.timeoutMs === "number" && Number.isFinite(body.timeoutMs) ? body.timeoutMs : DEFAULT_WAIT_MS;
86562
+ const timeoutMs = Math.min(Math.max(rawTimeout, MIN_WAIT_MS), MAX_WAIT_MS);
86563
+ const require2 = body.kind === "code" ? "code" : body.kind === "link" ? "link" : "signal";
86564
+ try {
86565
+ const stats = { filteredOut: 0 };
86566
+ const msg = await reader.waitForMessage({
86567
+ toAddress: identity.email,
86568
+ sinceMs,
86569
+ sinceUid,
86570
+ sinceUidValidity,
86571
+ maxWaitMs: timeoutMs,
86572
+ require: require2,
86573
+ stats
86574
+ });
86575
+ const publicLink = msg && msg.link && (0, inbox_tools_js_1.isPublicHttpUrl)(msg.link) ? msg.link : null;
86576
+ const decision = decideInboxWaitResponse(msg, publicLink);
86577
+ if (!decision.found) {
86578
+ opts.log?.(`\u{1F4ED} Inbox broker: no usable verification email for ${identity.tag} within ${Math.round(timeoutMs / 1e3)}s`);
86579
+ return { status: 200, body: { found: false } };
86580
+ }
86581
+ opts.log?.(`\u{1F4EC} Inbox broker: email for ${identity.tag} (code=${decision.code ? "yes" : "no"}, link=${decision.link ? "yes" : "no"})`);
86582
+ return { status: 200, body: { ...decision } };
86583
+ } catch (error2) {
86584
+ if ((0, verification_inbox_js_1.isInboxAuthFailure)(error2)) {
86585
+ authDeadReason = error2 instanceof Error ? error2.message : String(error2);
86586
+ return { status: 200, body: { found: false, error: "inbox authentication failed (revoked/wrong app password)" } };
86587
+ }
86588
+ const message = error2 instanceof Error ? error2.message : String(error2);
86589
+ opts.log?.(`\u26A0 Inbox broker wait failed: ${message}`);
86590
+ return { status: 200, body: { found: false, error: `inbox read failed: ${message}` } };
86591
+ } finally {
86592
+ waiting = false;
86593
+ }
86594
+ };
86595
+ return {
86596
+ identity,
86597
+ childEnv: () => ({
86598
+ [exports.TEST_INBOX_EMAIL_ENV]: identity.email,
86599
+ [exports.TEST_INBOX_PASSWORD_ENV]: identity.password
86600
+ }),
86601
+ handleWait,
86602
+ close: () => reader.close()
86603
+ };
86604
+ }
86605
+ }
86606
+ });
86607
+
85807
86608
  // ../runner-core/dist/services/playwright-native.js
85808
86609
  var require_playwright_native = __commonJS({
85809
86610
  "../runner-core/dist/services/playwright-native.js"(exports) {
@@ -85861,6 +86662,9 @@ var require_playwright_native = __commonJS({
85861
86662
  var har_stream_js_1 = require_har_stream();
85862
86663
  var api_auth_headers_js_1 = require_api_auth_headers();
85863
86664
  var run_callback_proxy_js_1 = require_run_callback_proxy();
86665
+ var inbox_stamp_js_1 = require_inbox_stamp();
86666
+ var test_inbox_broker_js_1 = require_test_inbox_broker();
86667
+ var verification_inbox_js_1 = require_verification_inbox();
85864
86668
  var api_auth_headers_js_2 = require_api_auth_headers();
85865
86669
  Object.defineProperty(exports, "parseApiAuthHeaders", { enumerable: true, get: function() {
85866
86670
  return api_auth_headers_js_2.parseApiAuthHeaders;
@@ -85895,17 +86699,20 @@ var require_playwright_native = __commonJS({
85895
86699
  let runDir = null;
85896
86700
  let standingNeedleMatcher = null;
85897
86701
  let callbackProxy;
86702
+ let inboxBroker;
85898
86703
  try {
85899
86704
  runDir = (0, path_1.join)(RUNS_DIR, (0, crypto_1.randomUUID)());
85900
86705
  await (0, promises_1.mkdir)(runDir, { recursive: true });
85901
86706
  const isApiTest2 = playwrightCode.includes("@api") || playwrightCode.includes("{ request }") && !playwrightCode.includes("{ page");
86707
+ const usesInboxHelper = !isApiTest2 && (0, inbox_stamp_js_1.codeUsesInboxHelper)(playwrightCode);
85902
86708
  const effectiveStandingMode = isApiTest2 ? authStandingMode ?? "legacy-if-configured" : "none";
85903
86709
  const resolvedExtraHeaders = (0, api_auth_headers_js_1.resolveApiAuthHeaders)({
85904
86710
  explicit: extraHttpHeaders,
85905
86711
  standingHeadersMode: effectiveStandingMode,
85906
86712
  apiAuthHeadersVar: testVariables?.API_AUTH_HEADERS
85907
86713
  });
85908
- standingNeedleMatcher = (0, shared_1.buildNeedleMatcher)(resolvedExtraHeaders ? Object.values(resolvedExtraHeaders) : []);
86714
+ const needleValues = resolvedExtraHeaders ? Object.values(resolvedExtraHeaders) : [];
86715
+ standingNeedleMatcher = (0, shared_1.buildNeedleMatcher)(needleValues);
85909
86716
  const cssDisableSetup = `
85910
86717
  const __qaLiveBrowserStateFile = process.env.VALIDATE_LIVE_BROWSER_STATE_PATH;
85911
86718
  let __qaLiveBrowserInterval: ReturnType<typeof setInterval> | null = null;
@@ -86144,7 +86951,7 @@ test.afterEach(async ({ page }, testInfo) => {
86144
86951
  module.exports = {
86145
86952
  testDir: ${JSON.stringify(absTestDir)},
86146
86953
  outputDir: ${JSON.stringify(absOutputDir)},
86147
- timeout: 60000,
86954
+ timeout: ${usesInboxHelper ? 24e4 : 6e4},
86148
86955
  retries: 0,
86149
86956
  use: {
86150
86957
  baseURL: ${JSON.stringify(baseUrl)},
@@ -86170,6 +86977,27 @@ module.exports = {
86170
86977
  const isolation = await (0, chromium_isolation_js_1.createChromiumIsolation)(runDir);
86171
86978
  const processClaimEpoch = Number(process.env.CLAIM_EPOCH);
86172
86979
  const callbackClaimEpoch = Number.isSafeInteger(validateContext?.claimEpoch) ? validateContext?.claimEpoch : Number.isSafeInteger(processClaimEpoch) && processClaimEpoch >= 0 ? processClaimEpoch : void 0;
86980
+ if (usesInboxHelper && validateContext?.runId) {
86981
+ const inboxConfig = (0, verification_inbox_js_1.loadVerificationInboxConfig)();
86982
+ if (!inboxConfig) {
86983
+ logs2.push("\u{1F4EA} Test uses __vqaWaitForEmail but this runner has no verification inbox configured \u2014 the helper will fail as CONFIG_ISSUE");
86984
+ } else {
86985
+ try {
86986
+ inboxBroker = await (0, test_inbox_broker_js_1.startTestInboxBroker)({
86987
+ config: inboxConfig,
86988
+ baseUrl,
86989
+ projectId: options.projectId ?? null,
86990
+ runId: validateContext.runId,
86991
+ log: (line) => logs2.push(line)
86992
+ });
86993
+ standingNeedleMatcher = (0, shared_1.buildNeedleMatcher)([...needleValues, inboxBroker.identity.password]);
86994
+ } catch (error3) {
86995
+ logs2.push(`\u26A0 Inbox broker failed to start: ${error3 instanceof Error ? error3.message : String(error3)}`);
86996
+ }
86997
+ }
86998
+ } else if (usesInboxHelper) {
86999
+ logs2.push("\u{1F4EA} Test uses __vqaWaitForEmail but no run callback context is available \u2014 the helper will fail as CONFIG_ISSUE");
87000
+ }
86173
87001
  if (validateContext?.runId && validateContext.serverUrl && validateContext.authHeader) {
86174
87002
  try {
86175
87003
  callbackProxy = await (0, run_callback_proxy_js_1.startRunCallbackProxy)({
@@ -86178,7 +87006,8 @@ module.exports = {
86178
87006
  upstreamAuthHeader: validateContext.authHeader,
86179
87007
  taskArn: process.env.VALIDATE_TASK_ARN,
86180
87008
  claimEpoch: callbackClaimEpoch,
86181
- authConfigHash: process.env.VALIDATE_AUTH_CONFIG_HASH
87009
+ authConfigHash: process.env.VALIDATE_AUTH_CONFIG_HASH,
87010
+ ...inboxBroker ? { localRoutes: { [test_inbox_broker_js_1.INBOX_WAIT_ROUTE]: inboxBroker.handleWait } } : {}
86182
87011
  });
86183
87012
  } catch (error3) {
86184
87013
  logs2.push(`Callback proxy unavailable: ${error3 instanceof Error ? error3.message : String(error3)}`);
@@ -86205,14 +87034,19 @@ module.exports = {
86205
87034
  // Preserve an explicit shared-auth kill-switch in the child. Unset defaults on in
86206
87035
  // both parent and generated __vqaLogin code; "false" must still cross this process
86207
87036
  // boundary to disable the early publish POST. It remains a per-run toggle.
86208
- ...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {}
87037
+ ...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {},
87038
+ // Register-and-verify tests: the run's minted registration identity. An
87039
+ // address + a DERIVED throwaway-app password — never the IMAP secret,
87040
+ // which stays in this parent (test-inbox-broker.ts). Useless without the
87041
+ // loopback proxy the helper reads mail through, hence the guard.
87042
+ ...inboxBroker && callbackProxy ? inboxBroker.childEnv() : {}
86209
87043
  };
86210
87044
  delete childTestVariables.API_AUTH_HEADERS;
86211
87045
  logs2.push("\u{1F680} Spawning playwright test...");
86212
87046
  const stopLiveBrowserPolling = liveBrowserStatePath && liveBrowserHandle ? startLiveBrowserStatePolling(liveBrowserStatePath, liveBrowserHandle, onLiveBrowserSnapshot) : null;
86213
87047
  let playResult;
86214
87048
  try {
86215
- playResult = await runPlaywright(configPath, childTestVariables, trustedRuntimeEnv);
87049
+ playResult = await runPlaywright(configPath, childTestVariables, trustedRuntimeEnv, usesInboxHelper ? 3e5 : void 0);
86216
87050
  } finally {
86217
87051
  await stopLiveBrowserPolling?.();
86218
87052
  }
@@ -86303,6 +87137,8 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
86303
87137
  } finally {
86304
87138
  await callbackProxy?.close().catch(() => {
86305
87139
  });
87140
+ await inboxBroker?.close().catch(() => {
87141
+ });
86306
87142
  if (runDir) {
86307
87143
  await (0, promises_1.rm)(runDir, { recursive: true, force: true }).catch(() => {
86308
87144
  });
@@ -86354,7 +87190,7 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
86354
87190
  stopped = true;
86355
87191
  };
86356
87192
  }
86357
- function runPlaywright(configPath, testVariables, trustedRuntimeEnv) {
87193
+ function runPlaywright(configPath, testVariables, trustedRuntimeEnv, killAfterMs) {
86358
87194
  return new Promise((resolvePromise) => {
86359
87195
  const sandboxedEnv = buildNativePlaywrightChildEnv(process.env, testVariables, trustedRuntimeEnv);
86360
87196
  const proc = (0, spawn_with_tree_kill_js_1.spawnDetachedGroup)(process.execPath, [PW_CLI, "test", "--config", configPath], {
@@ -86372,11 +87208,13 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
86372
87208
  proc.stderr?.on("data", (chunk) => {
86373
87209
  stderr += chunk.toString();
86374
87210
  });
86375
- const cancelKill = (0, spawn_with_tree_kill_js_1.scheduleTreeKill)(proc, 12e4, {
87211
+ const killMs = killAfterMs ?? 12e4;
87212
+ const cancelKill = (0, spawn_with_tree_kill_js_1.scheduleTreeKill)(proc, killMs, {
86376
87213
  graceMs: 1e4,
86377
87214
  onTimeout: () => {
86378
87215
  timedOut = true;
86379
- stderr += "\n[playwright killed after 120s safety timeout]";
87216
+ stderr += `
87217
+ [playwright killed after ${Math.round(killMs / 1e3)}s safety timeout]`;
86380
87218
  }
86381
87219
  });
86382
87220
  const settle = (result) => {
@@ -90075,402 +90913,6 @@ var require_anchor_harvester = __commonJS({
90075
90913
  }
90076
90914
  });
90077
90915
 
90078
- // ../runner-core/dist/services/discovery-lint-flags.js
90079
- var require_discovery_lint_flags = __commonJS({
90080
- "../runner-core/dist/services/discovery-lint-flags.js"(exports) {
90081
- "use strict";
90082
- Object.defineProperty(exports, "__esModule", { value: true });
90083
- exports.structuralLocatorLintMode = structuralLocatorLintMode;
90084
- exports.isPwMethodPreflightEnabled = isPwMethodPreflightEnabled;
90085
- exports.isVqaLoginDedupeEnabled = isVqaLoginDedupeEnabled;
90086
- function strictOn(value) {
90087
- return value === "true" || value === "1";
90088
- }
90089
- function structuralLocatorLintMode() {
90090
- const v = (process.env.DISCOVERY_STRUCTURAL_LOCATOR_LINT || "").trim().toLowerCase();
90091
- if (v === "warn")
90092
- return "warn";
90093
- if (v === "true" || v === "1" || v === "enforce")
90094
- return "enforce";
90095
- return "off";
90096
- }
90097
- function isPwMethodPreflightEnabled() {
90098
- return strictOn(process.env.DISCOVERY_PW_METHOD_PREFLIGHT);
90099
- }
90100
- function isVqaLoginDedupeEnabled() {
90101
- return strictOn(process.env.DISCOVERY_VQALOGIN_DEDUPE);
90102
- }
90103
- }
90104
- });
90105
-
90106
- // ../runner-core/dist/services/auth-recipe.js
90107
- var require_auth_recipe = __commonJS({
90108
- "../runner-core/dist/services/auth-recipe.js"(exports) {
90109
- "use strict";
90110
- Object.defineProperty(exports, "__esModule", { value: true });
90111
- exports.RESERVED_RUNNER_ENV_VARS = exports.AUTH_MARKER_VERSION = void 0;
90112
- exports.computeRecipeHash = computeRecipeHash;
90113
- exports.buildAuthRecipe = buildAuthRecipe;
90114
- exports.authProofLocator = authProofLocator;
90115
- exports.urlProofPredicateExpr = urlProofPredicateExpr;
90116
- exports.renderAuthProofAssertion = renderAuthProofAssertion;
90117
- exports.renderAuthFn = renderAuthFn;
90118
- exports.isReservedRunnerEnvVar = isReservedRunnerEnvVar;
90119
- exports.absoluteRoute = absoluteRoute;
90120
- exports.renderReplayClickArrow = renderReplayClickArrow;
90121
- exports.renderReplaySettleArrow = renderReplaySettleArrow;
90122
- exports.renderAuthUrlWaitLine = renderAuthUrlWaitLine;
90123
- exports.commandToReplayStep = commandToReplayStep;
90124
- exports.buildReplayPlan = buildReplayPlan;
90125
- exports.extractAuthRegion = extractAuthRegion;
90126
- exports.stripAuthRegions = stripAuthRegions;
90127
- exports.ensureExpectImported = ensureExpectImported;
90128
- exports.ensureLoginCall = ensureLoginCall;
90129
- exports.upsertAuthBlock = upsertAuthBlock;
90130
- exports.buildHealAuthStamp = buildHealAuthStamp;
90131
- var crypto_1 = __require("crypto");
90132
- var shared_1 = (init_dist(), __toCommonJS(dist_exports));
90133
- var discovery_lint_flags_js_1 = require_discovery_lint_flags();
90134
- exports.AUTH_MARKER_VERSION = "v3";
90135
- var START_MARKER = `// >>> VALIDATE_QA_AUTH ${exports.AUTH_MARKER_VERSION} \u2014 generated, do not edit >>>`;
90136
- var END_MARKER = `// <<< VALIDATE_QA_AUTH ${exports.AUTH_MARKER_VERSION} <<<`;
90137
- var SHARED_AUTH_RUNTIME_ENABLED = `!/^false$/i.test((process.env.SHARED_AUTH_INJECT ?? '').trim())`;
90138
- var AUTH_REGION_RE = /\/\/ >>> VALIDATE_QA_AUTH[^\n]*\r?\n[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH[^\n]*\r?\n?/g;
90139
- function computeRecipeHash(input) {
90140
- const canonical = JSON.stringify({
90141
- preNavRoute: input.preNavRoute,
90142
- preNavCmds: input.preNavCmds,
90143
- route: input.route,
90144
- cmds: input.cmds,
90145
- redirectRoute: input.redirectRoute,
90146
- authProof: input.authProof
90147
- });
90148
- return (0, crypto_1.createHash)("sha256").update(canonical).digest("hex").slice(0, 12);
90149
- }
90150
- function buildAuthRecipe(login, authProof, verifiedAt) {
90151
- const preNavRoute = login.preNavRoute ?? "/";
90152
- const preNavCmds = login.preNavCmds ?? [];
90153
- const recipeHash = computeRecipeHash({
90154
- preNavRoute,
90155
- preNavCmds,
90156
- route: login.route,
90157
- cmds: login.cmds,
90158
- redirectRoute: login.redirectRoute,
90159
- authProof
90160
- });
90161
- return {
90162
- version: 1,
90163
- recipeHash,
90164
- preNavRoute,
90165
- preNavCmds,
90166
- route: login.route,
90167
- cmds: login.cmds,
90168
- redirectRoute: login.redirectRoute,
90169
- authProof,
90170
- replayPlan: login.replayPlan ?? [],
90171
- verifiedAt
90172
- };
90173
- }
90174
- function authProofLocator(proof) {
90175
- if (proof.kind === "url") {
90176
- throw new Error("authProofLocator: url proof has no element locator; callers must branch on proof.kind");
90177
- }
90178
- if (proof.kind === "testid") {
90179
- return `page.getByTestId(${JSON.stringify(proof.testId)})`;
90180
- }
90181
- if (proof.name) {
90182
- return `page.getByRole(${JSON.stringify(proof.role)}, { name: ${JSON.stringify(proof.name)} })`;
90183
- }
90184
- return `page.getByRole(${JSON.stringify(proof.role)}).first()`;
90185
- }
90186
- function stripToPathname(route2) {
90187
- return route2.replace(/[?#].*$/, "").replace(/\/+$/, "") || "/";
90188
- }
90189
- function urlProofPredicateExpr(route2) {
90190
- const p = stripToPathname(route2);
90191
- if (p === "/")
90192
- return `() => false`;
90193
- const r = JSON.stringify(p);
90194
- return `(u) => u.pathname === ${r} || u.pathname.startsWith(${r} + '/')`;
90195
- }
90196
- function renderAuthProofAssertion(proof) {
90197
- if (proof.kind === "url") {
90198
- return `await page.waitForURL(${urlProofPredicateExpr(proof.route)}, { timeout: 15000 });`;
90199
- }
90200
- return `await expect(${authProofLocator(proof)}).toBeVisible({ timeout: 15000 });`;
90201
- }
90202
- function normalizeCmd(cmd) {
90203
- let c = cmd.trim();
90204
- if (c.endsWith(";"))
90205
- c = c.slice(0, -1);
90206
- return ` ${c};`;
90207
- }
90208
- function renderAuthFn(recipe, opts) {
90209
- const lines = [];
90210
- lines.push(START_MARKER);
90211
- lines.push(`async function __vqaLogin(page) {`);
90212
- if (opts?.warmGuard) {
90213
- const warmLanding = recipe.redirectRoute ?? (recipe.authProof.kind === "url" ? recipe.authProof.route : recipe.preNavRoute);
90214
- const loginRoute = recipe.route ?? recipe.preNavRoute;
90215
- lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED}) {`);
90216
- lines.push(` await page.goto(${JSON.stringify(warmLanding)}, { waitUntil: 'domcontentloaded' }).catch(() => {});`);
90217
- if (recipe.authProof.kind === "url") {
90218
- const pred = urlProofPredicateExpr(recipe.authProof.route);
90219
- lines.push(` await page.waitForURL((u) => !(${pred})(u), { timeout: 2000 }).catch(() => {});`);
90220
- lines.push(` if ((${pred})(new URL(page.url())) && !(await page.locator('input[type="password"]').first().isVisible().catch(() => false))) return;`);
90221
- } else if (loginRoute) {
90222
- lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false) && new URL(page.url()).pathname !== new URL(${JSON.stringify(loginRoute)}, page.url()).pathname) return;`);
90223
- } else {
90224
- lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false)) return;`);
90225
- }
90226
- lines.push(` }`);
90227
- }
90228
- lines.push(` await page.goto(${JSON.stringify(recipe.preNavRoute)}, { waitUntil: 'domcontentloaded' });`);
90229
- for (const cmd of recipe.preNavCmds)
90230
- lines.push(normalizeCmd(cmd));
90231
- if (recipe.preNavCmds.length === 0 && recipe.route && recipe.route !== recipe.preNavRoute) {
90232
- lines.push(` await page.goto(${JSON.stringify(recipe.route)}, { waitUntil: 'domcontentloaded' });`);
90233
- }
90234
- for (const cmd of recipe.cmds)
90235
- lines.push(normalizeCmd(cmd));
90236
- lines.push(renderAuthUrlWaitLine(recipe.redirectRoute));
90237
- lines.push(` ${renderAuthProofAssertion(recipe.authProof)}`);
90238
- if (opts?.warmGuard) {
90239
- lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED} && process.env.AUTH_FILE) await page.context().storageState({ path: process.env.AUTH_FILE });`);
90240
- lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED} && process.env.VALIDATE_TASK_ARN && process.env.VALIDATE_CLAIM_EPOCH && process.env.VALIDATE_SERVER_URL && process.env.VALIDATE_RUN_ID) {`);
90241
- lines.push(` try {`);
90242
- lines.push(` const __vqaState = await page.context().storageState();`);
90243
- lines.push(` await page.request.post(process.env.VALIDATE_SERVER_URL + '/api/runner/runs/' + process.env.VALIDATE_RUN_ID + '/auth-state', { headers: { Authorization: process.env.VALIDATE_RUNNER_AUTH || '' }, data: { storageState: JSON.stringify(__vqaState), taskArn: process.env.VALIDATE_TASK_ARN, claimEpoch: Number(process.env.VALIDATE_CLAIM_EPOCH) }, timeout: 10000 });`);
90244
- lines.push(` } catch {}`);
90245
- lines.push(` }`);
90246
- }
90247
- lines.push(`}`);
90248
- lines.push(END_MARKER);
90249
- return lines.join("\n");
90250
- }
90251
- exports.RESERVED_RUNNER_ENV_VARS = /* @__PURE__ */ new Set([
90252
- "AUTH_FILE",
90253
- "SHARED_AUTH_INJECT",
90254
- "VALIDATE_RUN_ID",
90255
- "VALIDATE_CLAIM_EPOCH",
90256
- "VALIDATE_AUTH_CONFIG_HASH",
90257
- "VALIDATE_TASK_ARN",
90258
- "VALIDATE_RUNNER_AUTH",
90259
- "VALIDATE_SERVER_URL"
90260
- ]);
90261
- function isReservedRunnerEnvVar(key) {
90262
- return exports.RESERVED_RUNNER_ENV_VARS.has(key);
90263
- }
90264
- function absoluteRoute(route2, baseUrl) {
90265
- try {
90266
- return new URL(route2, baseUrl).href;
90267
- } catch {
90268
- return route2;
90269
- }
90270
- }
90271
- function renderReplayClickArrow(step) {
90272
- const loc = step.locator.trim();
90273
- if (step.kind === "wait") {
90274
- return `async (page) => {
90275
- await ${loc}.waitFor({ state: 'visible', timeout: 15000 });
90276
- }`;
90277
- }
90278
- if (step.kind === "press") {
90279
- return `async (page) => {
90280
- await ${loc}.press(${JSON.stringify(step.key ?? "Enter")});
90281
- }`;
90282
- }
90283
- return `async (page) => {
90284
- await ${loc}.click();
90285
- }`;
90286
- }
90287
- function renderReplaySettleArrow(proof) {
90288
- if (proof.kind === "url") {
90289
- return `async (page) => {
90290
- await page.waitForURL(${urlProofPredicateExpr(proof.route)}, { timeout: 15000 });
90291
- }`;
90292
- }
90293
- return `async (page) => {
90294
- await ${authProofLocator(proof)}.waitFor({ state: 'visible', timeout: 15000 });
90295
- }`;
90296
- }
90297
- function renderAuthUrlWaitLine(redirectRoute) {
90298
- const opts = `{ waitUntil: 'domcontentloaded', timeout: 8000 }`;
90299
- const tail = `.catch(() => { /* URL drift \u2014 the auth-proof assertion below is the gate */ });`;
90300
- const rr = redirectRoute ? stripToPathname(redirectRoute) : null;
90301
- if (rr && rr !== "/") {
90302
- const r = JSON.stringify(rr);
90303
- return ` await page.waitForURL((u) => u.pathname === ${r} || u.pathname.startsWith(${r} + '/'), ${opts})${tail}`;
90304
- }
90305
- if (rr === "/") {
90306
- return ` await page.waitForURL((u) => !/\\/(login|sign-?in)/i.test(u.pathname), ${opts})${tail}`;
90307
- }
90308
- return ` await page.waitForURL((u) => u.pathname !== '/' && !/\\/(login|sign-?in)/i.test(u.pathname), ${opts})${tail}`;
90309
- }
90310
- function locatorChainOf(cmd) {
90311
- const m = cmd.match(/\bpage\.[\s\S]*?(?=\.(?:fill|click|check|press|selectOption|setChecked|waitFor)\s*\()/);
90312
- return m ? m[0].trim() : null;
90313
- }
90314
- function parseRoleName(locator) {
90315
- const role = locator.match(/getByRole\(\s*['"`]([^'"`]+)['"`]/)?.[1];
90316
- const name = locator.match(/getByRole\([^)]*name:\s*['"`]([^'"`]+)['"`]/)?.[1] ?? locator.match(/getByLabel\(\s*['"`]([^'"`]+)['"`]/)?.[1] ?? locator.match(/getByPlaceholder\(\s*['"`]([^'"`]+)['"`]/)?.[1];
90317
- return { role, name };
90318
- }
90319
- function credentialKeyOf(cmd) {
90320
- return cmd.match(/\.fill\(\s*process\.env\.([A-Za-z_$][\w$]*)!?\s*\)/)?.[1] ?? null;
90321
- }
90322
- function commandToReplayStep(cmd) {
90323
- const chain = locatorChainOf(cmd);
90324
- if (!chain)
90325
- return null;
90326
- const credKey = credentialKeyOf(cmd);
90327
- if (cmd.includes(".fill(")) {
90328
- if (!credKey)
90329
- return null;
90330
- const { role, name } = parseRoleName(chain);
90331
- return { kind: "fill", credentialKey: credKey, locator: chain, role, name, element: name ?? role ?? "credential field" };
90332
- }
90333
- if (/\.waitFor\s*\(/.test(cmd))
90334
- return { kind: "wait", locator: chain };
90335
- const pressKey = cmd.match(/\.press\(\s*['"`]([^'"`]+)['"`]\s*\)/)?.[1];
90336
- if (pressKey)
90337
- return { kind: "press", locator: chain, key: pressKey };
90338
- if (/\.(?:click|check|setChecked|selectOption)\s*\(/.test(cmd))
90339
- return { kind: "click", locator: chain };
90340
- return null;
90341
- }
90342
- function buildReplayPlan(login) {
90343
- const plan = [];
90344
- const preNavRoute = login.preNavRoute ?? "/";
90345
- plan.push({ kind: "nav", route: preNavRoute });
90346
- for (const c of login.preNavCmds ?? []) {
90347
- const step = commandToReplayStep(c);
90348
- if (step)
90349
- plan.push(step);
90350
- }
90351
- if ((login.preNavCmds ?? []).length === 0 && login.route && login.route !== preNavRoute) {
90352
- plan.push({ kind: "nav", route: login.route });
90353
- }
90354
- for (const c of login.cmds) {
90355
- const step = commandToReplayStep(c);
90356
- if (step)
90357
- plan.push(step);
90358
- }
90359
- return plan;
90360
- }
90361
- function extractAuthRegion(code) {
90362
- const m = code.match(new RegExp(AUTH_REGION_RE.source));
90363
- return m ? m[0] : null;
90364
- }
90365
- function countAuthRegions(code) {
90366
- const m = code.match(AUTH_REGION_RE);
90367
- return m ? m.length : 0;
90368
- }
90369
- function stripAuthRegions(code) {
90370
- return code.replace(AUTH_REGION_RE, "");
90371
- }
90372
- var PW_IMPORT_RE = /import\s*\{([^}]*)\}\s*from\s*['"]@playwright\/test['"]\s*;?/;
90373
- function ensureExpectImported(code) {
90374
- const m = code.match(PW_IMPORT_RE);
90375
- if (!m)
90376
- return code;
90377
- const names = m[1].split(",").map((s) => s.trim()).filter(Boolean);
90378
- if (names.includes("expect"))
90379
- return code;
90380
- const next = [...names, "expect"];
90381
- return code.replace(PW_IMPORT_RE, `import { ${next.join(", ")} } from '@playwright/test';`);
90382
- }
90383
- function insertAfterPwImport(code, block) {
90384
- const m = code.match(PW_IMPORT_RE);
90385
- if (!m || m.index === void 0) {
90386
- return `${block}
90387
-
90388
- ${code.replace(/^\r?\n+/, "")}`;
90389
- }
90390
- const end = m.index + m[0].length;
90391
- const head = code.slice(0, end);
90392
- const tail = code.slice(end).replace(/^\r?\n+/, "");
90393
- return `${head}
90394
-
90395
- ${block}
90396
-
90397
- ${tail}`;
90398
- }
90399
- var TEST_OPENER_RE = /test(?:\.(?:only|skip|fixme))?\s*\(\s*(['"`])(?:[^\\]|\\.)*?\1\s*,\s*(?:\{[\s\S]*?\}\s*,\s*)?async\s*\(\s*\{[^}]*\}[^)]*\)\s*=>\s*\{/;
90400
- var LOGIN_CALL_RE = /await\s+__vqaLogin\s*\(\s*page\s*\)/;
90401
- function ensureLoginCall(code) {
90402
- if (code.match(new RegExp(TEST_OPENER_RE.source, "g")) === null)
90403
- return code;
90404
- let result = code;
90405
- if ((0, discovery_lint_flags_js_1.isVqaLoginDedupeEnabled)()) {
90406
- result = result.replace(/^[ \t]*await[ \t]+__vqaLogin[ \t]*\([ \t]*page[ \t]*\)[ \t]*;?[ \t]*\r?\n/gm, "");
90407
- }
90408
- const matches = [...result.matchAll(new RegExp(TEST_OPENER_RE.source, "g"))];
90409
- for (let i = matches.length - 1; i >= 0; i--) {
90410
- const m = matches[i];
90411
- if (m.index === void 0)
90412
- continue;
90413
- const insertAt = m.index + m[0].length;
90414
- const following = result.slice(insertAt, insertAt + 60);
90415
- if (/^\s*await\s+__vqaLogin\s*\(\s*page\s*\)/.test(following))
90416
- continue;
90417
- result = `${result.slice(0, insertAt)}
90418
- await __vqaLogin(page);${result.slice(insertAt)}`;
90419
- }
90420
- return result;
90421
- }
90422
- function upsertAuthBlock(code, ctx) {
90423
- if (ctx.mode === "generate") {
90424
- if (!ctx.recipe || !ctx.isAuthNeeded)
90425
- return code;
90426
- let result = stripAuthRegions(code);
90427
- result = result.replace(/^[ \t]*await[ \t]+__vqaLogin[ \t]*\([ \t]*page[ \t]*\)[ \t]*;?[ \t]*\r?\n/gm, "");
90428
- result = ensureExpectImported(result);
90429
- const warmGuard = (0, shared_1.isSharedAuthInjectEnabled)();
90430
- result = insertAfterPwImport(result, renderAuthFn(ctx.recipe, { warmGuard }));
90431
- result = ensureLoginCall(result);
90432
- const n = countAuthRegions(result);
90433
- if (n !== 1) {
90434
- throw new Error(`upsertAuthBlock(generate): expected exactly 1 auth region after stamp, got ${n}`);
90435
- }
90436
- if (!LOGIN_CALL_RE.test(result)) {
90437
- throw new Error("upsertAuthBlock(generate): stamped __vqaLogin but found no test() opener to inject the call");
90438
- }
90439
- return result;
90440
- }
90441
- try {
90442
- if (!ctx.preservedAuthRegion)
90443
- return code;
90444
- let result = ensureExpectImported(code);
90445
- if (countAuthRegions(result) >= 1) {
90446
- result = result.replace(AUTH_REGION_RE, "");
90447
- result = insertAfterPwImport(result, ctx.preservedAuthRegion);
90448
- } else {
90449
- result = insertAfterPwImport(result, ctx.preservedAuthRegion);
90450
- }
90451
- result = ensureLoginCall(result);
90452
- if (countAuthRegions(result) !== 1) {
90453
- throw new Error("heal re-stamp produced non-unique auth region");
90454
- }
90455
- return result;
90456
- } catch (err) {
90457
- const reason = err instanceof Error ? err.message : String(err);
90458
- ctx.onSignal?.({ type: "auth-stamp-degraded", reason });
90459
- return code;
90460
- }
90461
- }
90462
- function buildHealAuthStamp(originalCode, failureError, logs2) {
90463
- const region = extractAuthRegion(originalCode);
90464
- const loginFailed = !!failureError && /__vqaLogin/.test(failureError);
90465
- return {
90466
- mode: "heal",
90467
- preservedAuthRegion: region && !loginFailed ? region : null,
90468
- onSignal: logs2 ? (s) => logs2.push(`\u26A0 auth-stamp ${s.type}: ${s.reason}`) : void 0
90469
- };
90470
- }
90471
- }
90472
- });
90473
-
90474
90916
  // ../runner-core/dist/services/auth-replay.js
90475
90917
  var require_auth_replay = __commonJS({
90476
90918
  "../runner-core/dist/services/auth-replay.js"(exports) {
@@ -94347,9 +94789,41 @@ var require_discover_generator = __commonJS({
94347
94789
  var reveal_diff_js_1 = require_reveal_diff();
94348
94790
  var anchor_harvester_js_1 = require_anchor_harvester();
94349
94791
  var auth_recipe_js_1 = require_auth_recipe();
94792
+ var inbox_stamp_js_1 = require_inbox_stamp();
94350
94793
  var auth_replay_js_1 = require_auth_replay();
94351
94794
  var agentic_discovery_prompts_js_1 = require_agentic_discovery_prompts();
94352
94795
  var discovery_lint_flags_js_1 = require_discovery_lint_flags();
94796
+ var inbox_tools_js_1 = require_inbox_tools();
94797
+ var verification_inbox_js_1 = require_verification_inbox();
94798
+ var test_inbox_broker_js_1 = require_test_inbox_broker();
94799
+ var REGISTER_SCENARIO_RE = /\b(register|registration|sign[\s-]?up|create[\s-]?(a[n]?[\s-]?)?(new[\s-]?)?account|verify[\s-]?(your[\s-]?)?email|email[\s-]?verification|confirm[\s-]?(your[\s-]?)?(email|account)|activate[\s-]?(your[\s-]?)?account|new[\s-]?account)\b/i;
94800
+ function scenarioLooksLikeRegistration(scenario) {
94801
+ const hay = `${scenario.name ?? ""} ${scenario.description ?? ""} ${scenario.entryRoute ?? ""} ${(scenario.targetPages ?? []).join(" ")}`;
94802
+ return REGISTER_SCENARIO_RE.test(hay);
94803
+ }
94804
+ function buildEmailVerifyGenBlock(genEmail, genPassword) {
94805
+ return `
94806
+
94807
+ ## EMAIL VERIFICATION TEST \u2014 register a fresh account and verify it (LIVE, then EMIT)
94808
+ This is a signup / email-verification scenario and this runner CAN receive verification email. Prove the full flow live, then emit a test that re-does it on every run.
94809
+
94810
+ LIVE (to prove the flow while generating):
94811
+ - Register on the app's signup form using this throwaway account:
94812
+ - email: \`${genEmail}\`
94813
+ - password: \`${genPassword}\`
94814
+ Type them with browser_type and submit the form ONCE.
94815
+ - If the app then asks for an emailed code or a "verify your email" link, call \`wait_for_verification\` (poll it again if the first call returns found:false \u2014 mail can take 30\u201390s). If it returns a \`code\`, type it into the verification field. If it returns a \`link\`, use \`open_verification_link\`.
94816
+ - Reach the verified / signed-in state and confirm it with a browser_verify_* call, so your final assertion is proven.
94817
+
94818
+ EMIT (the code you put in finish_generate) \u2014 do NOT hardcode the throwaway values above:
94819
+ - Use \`process.env.${test_inbox_broker_js_1.TEST_INBOX_EMAIL_ENV}!\` for the email and \`process.env.${test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV}!\` for the password. The runner mints a FRESH real account for each execution and injects these \u2014 a hardcoded email would collide with "already registered" on re-runs.
94820
+ - Read the verification email at runtime with the injected helper (it is provided for you \u2014 do NOT define it, do NOT import it):
94821
+ - code flow: \`const code = await __vqaWaitForEmail('code');\` then type \`code\` into the verification field.
94822
+ - link flow: \`const link = await __vqaWaitForEmail('link');\` then \`await page.goto(link);\`.
94823
+ - if unsure: \`const email = await __vqaWaitForEmail();\` returns \`{ code, link, subject, from }\`.
94824
+ - Tag the test \`@email-verify\`. Emit EXACTLY ONE test() in this file (one registration per run).
94825
+ - End with a DURABLE assertion of the post-verification signed-in state (e.g. a Logout control, an account/dashboard landmark) in your OWN test body \u2014 the helper call is not itself a proof.`;
94826
+ }
94353
94827
  exports.BLOCKED_MCP_TOOLS_SCENARIO_GEN = /* @__PURE__ */ new Set([
94354
94828
  "browser_install",
94355
94829
  "browser_close",
@@ -95536,6 +96010,11 @@ ${testOpen}`;
95536
96010
  if (options?.authStamp) {
95537
96011
  result = (0, auth_recipe_js_1.upsertAuthBlock)(result, options.authStamp);
95538
96012
  }
96013
+ try {
96014
+ result = (0, inbox_stamp_js_1.upsertInboxBlock)(result);
96015
+ } catch {
96016
+ result = (0, inbox_stamp_js_1.stripInboxRegions)(result);
96017
+ }
95539
96018
  return result;
95540
96019
  }
95541
96020
  function ensureDiscoveredTag(tags) {
@@ -95622,6 +96101,16 @@ ${testOpen}`;
95622
96101
  logs2?.push(` \u2717 Rejected "${t.name}": JS parse error \u2014 ${msg}`);
95623
96102
  return false;
95624
96103
  }
96104
+ {
96105
+ const modelBody = (0, inbox_stamp_js_1.stripInboxRegions)(code);
96106
+ if (inbox_stamp_js_1.INBOX_HELPER_TOKEN_RE.test(modelBody)) {
96107
+ const openerCount = (stripStringsAndComments(modelBody).match(/\btest(?:\.(?:only|skip|fixme))?\s*\(/g) ?? []).length;
96108
+ if (openerCount > 1) {
96109
+ logs2?.push(` \u2717 Rejected "${t.name}": ${openerCount} test() blocks in a register-and-verify spec \u2014 the per-run registration email supports exactly one registration; emit ONE test() per spec`);
96110
+ return false;
96111
+ }
96112
+ }
96113
+ }
95625
96114
  const codeForArgOrderScan = stripStringsAndComments(code);
95626
96115
  if (/test\s*\(\s*,\s*(?:async\s*)?(?:\([^)]*\)|function\s*\([^)]*\))\s*(?:=>)?\s*\{[\s\S]*?\}\s*,\s*\{\s*(?:tags?|annotations)\s*:/.test(codeForArgOrderScan)) {
95627
96116
  logs2?.push(` \u2717 Rejected "${t.name}": test() arg order is wrong \u2014 Playwright signature is test(name, options, fn), AI emitted test(name, fn, options)`);
@@ -96442,6 +96931,23 @@ ${testOpen}`;
96442
96931
  return { offendingLines: offending };
96443
96932
  }
96444
96933
  var ACTION_METHODS = ["click", "dblclick", "tap", "fill", "type", "pressSequentially", "press", "selectOption", "hover", "check", "uncheck", "setInputFiles", "dragTo", "focus", "goto", "waitForURL"];
96934
+ var DYNAMIC_VERIFICATION_ARG = "<verification-dynamic>";
96935
+ function collectVerificationHelperVars(code) {
96936
+ const vars = /* @__PURE__ */ new Set();
96937
+ const assignRe = /(?:(?:const|let|var)\s+)?([A-Za-z_$][\w$]*)\s*=\s*await\s+__vqaWaitForEmail\s*\(/g;
96938
+ let m;
96939
+ while ((m = assignRe.exec(code)) !== null)
96940
+ vars.add(m[1]);
96941
+ const destructRe = /(?:const|let|var)\s*\{([^}]*)\}\s*=\s*await\s+__vqaWaitForEmail\s*\(/g;
96942
+ while ((m = destructRe.exec(code)) !== null) {
96943
+ for (const part of m[1].split(",")) {
96944
+ const name = part.split(":").pop()?.trim().replace(/\s.*$/, "");
96945
+ if (name && /^[A-Za-z_$][\w$]*$/.test(name))
96946
+ vars.add(name);
96947
+ }
96948
+ }
96949
+ return vars;
96950
+ }
96445
96951
  function extractStringArg(text) {
96446
96952
  const m = text.match(/^\s*(['"`])((?:\\.|(?!\1).)*)\1/);
96447
96953
  if (!m)
@@ -96711,6 +97217,8 @@ ${testOpen}`;
96711
97217
  if (!locatorsMatch(ledger.locator, submitted.locator))
96712
97218
  return false;
96713
97219
  if (ledger.method === "fill" || ledger.method === "type") {
97220
+ if (submitted.arg === DYNAMIC_VERIFICATION_ARG)
97221
+ return true;
96714
97222
  if (ledger.arg === "<env>" || ledger.arg === "<test-value>") {
96715
97223
  return submitted.arg === "<env>" || submitted.arg === "<test-value>";
96716
97224
  }
@@ -96805,6 +97313,25 @@ ${testOpen}`;
96805
97313
  }
96806
97314
  }
96807
97315
  const submittedSigs = extractActionSignatures(playwrightCode, credentials);
97316
+ const helperVars = collectVerificationHelperVars(playwrightCode);
97317
+ if (helperVars.size > 0) {
97318
+ const usesHelperVar = (text) => {
97319
+ for (const v of helperVars) {
97320
+ if (new RegExp(`\\b${v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(text))
97321
+ return true;
97322
+ }
97323
+ return false;
97324
+ };
97325
+ for (const sub of submittedSigs) {
97326
+ if ((sub.method === "fill" || sub.method === "type") && sub.arg && usesHelperVar(sub.arg)) {
97327
+ sub.arg = DYNAMIC_VERIFICATION_ARG;
97328
+ } else if (sub.method === "goto") {
97329
+ const gotoArg = sub.raw.match(/\.goto\s*\(\s*([^),]+)/);
97330
+ if (gotoArg && usesHelperVar(gotoArg[1]))
97331
+ sub.arg = DYNAMIC_VERIFICATION_ARG;
97332
+ }
97333
+ }
97334
+ }
96808
97335
  const offending = [];
96809
97336
  let firstGotoConsumed = false;
96810
97337
  let cursor = 0;
@@ -96812,6 +97339,8 @@ ${testOpen}`;
96812
97339
  for (const sub of submittedSigs) {
96813
97340
  if (sub.method === "waitForURL")
96814
97341
  continue;
97342
+ if (sub.method === "goto" && sub.arg === DYNAMIC_VERIFICATION_ARG)
97343
+ continue;
96815
97344
  if (sub.method === "goto" && !firstGotoConsumed) {
96816
97345
  firstGotoConsumed = true;
96817
97346
  if (expectedEntry !== null && sub.arg !== expectedEntry) {
@@ -97137,7 +97666,7 @@ expect(secondTitle).toBeTruthy();
97137
97666
  ${tailBlock}`;
97138
97667
  }
97139
97668
  function evaluateAmbiguityGate(finalCode, alreadyAttempted, hasRevisionBudget) {
97140
- const findings = (0, agentic_discovery_prompts_js_1.scanAmbiguousLocators)((0, auth_recipe_js_1.stripAuthRegions)(finalCode));
97669
+ const findings = (0, agentic_discovery_prompts_js_1.scanAmbiguousLocators)((0, auth_recipe_js_1.stripAuthRegions)((0, inbox_stamp_js_1.stripInboxRegions)(finalCode)));
97141
97670
  return { fire: findings.length > 0 && !alreadyAttempted && hasRevisionBudget, findings };
97142
97671
  }
97143
97672
  async function generateTestFromScenario(mcpClient, scenario, siteMap, baseUrl, credentials, aiClient, agentModel, logs2, onAICall, opts) {
@@ -97161,6 +97690,7 @@ ${tailBlock}`;
97161
97690
  }
97162
97691
  }
97163
97692
  }
97693
+ let genInboxReader = null;
97164
97694
  try {
97165
97695
  const startTime = Date.now();
97166
97696
  const groupId = `scenario-gen-${(0, crypto_1.randomUUID)().slice(0, 8)}`;
@@ -97211,12 +97741,28 @@ ${tailBlock}`;
97211
97741
  });
97212
97742
  const baseSkeletonHadRegression = tags.includes("@regression");
97213
97743
  const hasVerifiedRecipe = !!opts?.recipe && tags.includes("@needs-auth");
97214
- const systemPrompt = buildScenarioSystemPrompt(scenario, baseUrl, credentials, tags, siteMap.pages, { appBrief: opts?.appBrief, availableVariableKeys: opts?.availableVariableKeys, hasVerifiedRecipe, knowledgeContext: opts?.knowledgeContext });
97744
+ const projectHasRegisterIntent = credentials?.DISCOVERY_SHOULD_REGISTER === "true";
97745
+ const inboxGenConfig = (0, discovery_lint_flags_js_1.isTestgenEmailVerifyEnabled)() && projectHasRegisterIntent && !hasVerifiedRecipe && scenarioLooksLikeRegistration(scenario) ? (0, verification_inbox_js_1.loadVerificationInboxConfig)() : null;
97746
+ let genInboxState = null;
97747
+ let genInboxCredentials = credentials;
97748
+ let genInboxSecret = null;
97749
+ if (inboxGenConfig) {
97750
+ const genTag = `vqag${(0, crypto_1.createHash)("sha256").update(`testgen|${(0, verification_inbox_js_1.seedToHostKey)(baseUrl)}|${groupId}`).digest("hex").slice(0, 10)}`;
97751
+ const genEmail = (0, verification_inbox_js_1.applyPlusTag)(inboxGenConfig.address, genTag);
97752
+ const genPassword = (0, verification_inbox_js_1.deriveRegistrationPassword)(genEmail);
97753
+ genInboxReader = new verification_inbox_js_1.VerificationInboxReader(inboxGenConfig);
97754
+ genInboxState = (0, inbox_tools_js_1.createInboxRunState)(inboxGenConfig, { tag: genTag, passwordKey: test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV });
97755
+ genInboxCredentials = { ...credentials, [test_inbox_broker_js_1.TEST_INBOX_EMAIL_ENV]: genEmail, [test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV]: genPassword };
97756
+ genInboxSecret = genPassword;
97757
+ openaiTools.push(...inbox_tools_js_1.INBOX_TOOLS);
97758
+ logs2.push(` \u{1F4EC} Email-verification generation ON for "${scenario.name}" \u2014 registering throwaway ${genEmail}`);
97759
+ }
97760
+ const systemPrompt = buildScenarioSystemPrompt(scenario, baseUrl, credentials, tags, siteMap.pages, { appBrief: opts?.appBrief, availableVariableKeys: opts?.availableVariableKeys, hasVerifiedRecipe, knowledgeContext: opts?.knowledgeContext }) + (genInboxState ? buildEmailVerifyGenBlock(genInboxCredentials[test_inbox_broker_js_1.TEST_INBOX_EMAIL_ENV], genInboxCredentials[test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV]) : "");
97215
97761
  const messages = [
97216
97762
  { role: "system", content: systemPrompt },
97217
97763
  {
97218
97764
  role: "user",
97219
- content: hasVerifiedRecipe ? `Start generating the test for scenario: "${scenario.name}". You are ALREADY logged in \u2014 \`await __vqaLogin(page);\` is injected as the first line of your test body, so do NOT author any login steps. Begin from the authenticated app and reach ${baseUrl}${findFirstNonAuthRoute(scenario.targetPages) || scenario.targetPages[0] || "/"} via visible navigation or a direct goto. Execute each step from the scenario plan, verifying with browser_verify_* tools at transition points.` : tags.includes("@needs-auth") ? `Start generating the test for scenario: "${scenario.name}". Begin at ${baseUrl}${scenario.entryRoute}. Complete the AUTH SETUP from the app's own visible flow (do NOT deep-link directly to auth unless the start route exposes no usable entry), then continue to ${baseUrl}${findFirstNonAuthRoute(scenario.targetPages) || scenario.targetPages[0] || "/"}. Execute each step from the scenario plan, verifying with browser_verify_* tools at transition points.` : `Start generating the test for scenario: "${scenario.name}". Begin by navigating to ${baseUrl}${scenario.entryRoute} using browser_navigate. From there, use the app's own navigation to reach the scenario flow. Do NOT deep-link directly to ${baseUrl}${scenario.targetPages[0] || "/"} unless the start route immediately redirects there or no visible navigation path exists. Then execute each step from the scenario plan, verifying with browser_verify_* tools at transition points.`
97765
+ content: genInboxState ? `Start generating the register-and-verify test for scenario: "${scenario.name}". Begin at ${baseUrl}${scenario.entryRoute}. Follow the "EMAIL VERIFICATION TEST" section: register the throwaway account LIVE, complete email verification via wait_for_verification, reach the signed-in state and verify it \u2014 then emit a test that uses process.env.${test_inbox_broker_js_1.TEST_INBOX_EMAIL_ENV}!/process.env.${test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV}! and __vqaWaitForEmail(...) as instructed.` : hasVerifiedRecipe ? `Start generating the test for scenario: "${scenario.name}". You are ALREADY logged in \u2014 \`await __vqaLogin(page);\` is injected as the first line of your test body, so do NOT author any login steps. Begin from the authenticated app and reach ${baseUrl}${findFirstNonAuthRoute(scenario.targetPages) || scenario.targetPages[0] || "/"} via visible navigation or a direct goto. Execute each step from the scenario plan, verifying with browser_verify_* tools at transition points.` : tags.includes("@needs-auth") ? `Start generating the test for scenario: "${scenario.name}". Begin at ${baseUrl}${scenario.entryRoute}. Complete the AUTH SETUP from the app's own visible flow (do NOT deep-link directly to auth unless the start route exposes no usable entry), then continue to ${baseUrl}${findFirstNonAuthRoute(scenario.targetPages) || scenario.targetPages[0] || "/"}. Execute each step from the scenario plan, verifying with browser_verify_* tools at transition points.` : `Start generating the test for scenario: "${scenario.name}". Begin by navigating to ${baseUrl}${scenario.entryRoute} using browser_navigate. From there, use the app's own navigation to reach the scenario flow. Do NOT deep-link directly to ${baseUrl}${scenario.targetPages[0] || "/"} unless the start route immediately redirects there or no visible navigation path exists. Then execute each step from the scenario plan, verifying with browser_verify_* tools at transition points.`
97220
97766
  }
97221
97767
  ];
97222
97768
  let iteration = 0;
@@ -97344,6 +97890,15 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
97344
97890
  const iterStart = Date.now();
97345
97891
  const requestMessageCount = messages.length;
97346
97892
  const requestMessages = messages.slice();
97893
+ const scrubAudit = (payload) => {
97894
+ if (!genInboxSecret)
97895
+ return payload;
97896
+ try {
97897
+ return JSON.parse(JSON.stringify(payload).split(genInboxSecret).join("[REDACTED:VQA_TEST_ACCOUNT_PASSWORD]"));
97898
+ } catch {
97899
+ return payload;
97900
+ }
97901
+ };
97347
97902
  let response;
97348
97903
  try {
97349
97904
  response = await (0, ai_retry_js_1.resilientAICall)((signal, arm) => (0, ai_queue_js_1.queuedAICall)(() => {
@@ -97385,7 +97940,7 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
97385
97940
  messages.push(assistantMessage);
97386
97941
  if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
97387
97942
  try {
97388
- onAICall?.({
97943
+ onAICall?.(scrubAudit({
97389
97944
  iteration,
97390
97945
  groupId,
97391
97946
  callType: "scenarioGenerate",
@@ -97399,7 +97954,7 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
97399
97954
  requestMessageCount,
97400
97955
  requestMessages,
97401
97956
  ledger: actionLedger.slice()
97402
- });
97957
+ }));
97403
97958
  } catch {
97404
97959
  }
97405
97960
  logs2.push(` No tool calls \u2014 agent stopped`);
@@ -97426,7 +97981,7 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
97426
97981
  const suspectedBugDeclared = toolArgs.suspectedBug === true;
97427
97982
  const bugDescription = String(toolArgs.bugDescription || "").slice(0, 500).replace(/[\u0000-\u001F\u007F]+/g, " ").replace(/\s+/g, " ").trim();
97428
97983
  const testObj = { name, description, playwrightCode: code, tags };
97429
- const validated = validateTests([testObj], logs2, { sensitiveEnvMap: credentials, authRecipe: opts?.recipe ?? null, basePath: gotoBasePathFromBaseUrl(baseUrl) });
97984
+ const validated = validateTests([testObj], logs2, { sensitiveEnvMap: genInboxCredentials, authRecipe: opts?.recipe ?? null, basePath: gotoBasePathFromBaseUrl(baseUrl) });
97430
97985
  if (validated.length > 0) {
97431
97986
  if (actionLedger.length > lastRenderedLedgerEntryCount && actionLedger.length > 0) {
97432
97987
  const renderedLedger = renderSessionRecording(actionLedger, credentials);
@@ -97478,7 +98033,7 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
97478
98033
  });
97479
98034
  continue;
97480
98035
  }
97481
- const actionCheck = validateFinalActionFidelity(code, actionLedger, credentials, { entryRoute: scenario.entryRoute });
98036
+ const actionCheck = validateFinalActionFidelity(code, actionLedger, genInboxCredentials, { entryRoute: scenario.entryRoute });
97482
98037
  if (actionCheck.offendingActions.length > 0 && !actionRevisionAttempted) {
97483
98038
  actionRevisionAttempted = true;
97484
98039
  const renderedLedger = actionLedger.length > 0 ? renderSessionRecording(actionLedger, credentials) : "";
@@ -97791,6 +98346,15 @@ Do NOT assert that the button/tab/heading you interacted with is merely visible
97791
98346
  });
97792
98347
  continue;
97793
98348
  }
98349
+ if (genInboxReader && genInboxState && inbox_tools_js_1.INBOX_TOOL_NAMES.has(toolName)) {
98350
+ const inboxResult = await (0, inbox_tools_js_1.dispatchInboxTool)(toolName, toolArgs, {
98351
+ reader: genInboxReader,
98352
+ state: genInboxState,
98353
+ mcpClient
98354
+ });
98355
+ toolResults.push({ role: "tool", tool_call_id: toolCall.id, content: inboxResult.content });
98356
+ continue;
98357
+ }
97794
98358
  let toolResultContent = "";
97795
98359
  try {
97796
98360
  const mcpResult = await mcpClient.callTool({ name: toolName, arguments: toolArgs });
@@ -97953,7 +98517,7 @@ WARNING: index-based locator (.nth/.first) \u2014 order-dependent and fragile on
97953
98517
  tool: assistantMessage.tool_calls?.find((tc) => tc.id === tr.tool_call_id)?.function.name ?? "unknown",
97954
98518
  result: typeof tr.content === "string" ? tr.content.slice(0, 5e4) : String(tr.content).slice(0, 5e4)
97955
98519
  }));
97956
- onAICall?.({
98520
+ onAICall?.(scrubAudit({
97957
98521
  iteration,
97958
98522
  groupId,
97959
98523
  callType: "scenarioGenerate",
@@ -97969,7 +98533,7 @@ WARNING: index-based locator (.nth/.first) \u2014 order-dependent and fragile on
97969
98533
  requestMessageCount,
97970
98534
  requestMessages,
97971
98535
  ledger: actionLedger.slice()
97972
- });
98536
+ }));
97973
98537
  } catch {
97974
98538
  }
97975
98539
  messages.push(...toolResults);
@@ -97977,8 +98541,12 @@ WARNING: index-based locator (.nth/.first) \u2014 order-dependent and fragile on
97977
98541
  if (!result) {
97978
98542
  logs2.push(` Scenario generation did not produce a test after ${iteration} iterations`);
97979
98543
  }
98544
+ await genInboxReader?.close().catch(() => {
98545
+ });
97980
98546
  return result;
97981
98547
  } catch (err) {
98548
+ await genInboxReader?.close().catch(() => {
98549
+ });
97982
98550
  const msg = err instanceof Error ? err.message : String(err);
97983
98551
  logs2.push(` Scenario generation failed: ${msg}`);
97984
98552
  if ((0, ai_retry_js_1.isAIProviderError)(err)) {
@@ -324913,7 +325481,7 @@ ${currentSummary}`;
324913
325481
  const distinctPathsVisited = new Set([...state2.visitedRoutes].map((r) => (0, exploration_parser_js_1.normalizeRouteForFocusScope)(r))).size;
324914
325482
  const fb = (0, survey_link_frontier_js_1.evaluateFinishBounce)(finishBounce, distinctPathsVisited);
324915
325483
  finishBounce = fb.state;
324916
- const forceAcceptFinish = fb.escape;
325484
+ const forceAcceptFinish = fb.escape && !finishStreakCountedThisBatch;
324917
325485
  if (forceAcceptFinish) {
324918
325486
  const stuck = adaptiveActive ? adaptiveDebtRoutes() : (0, survey_link_frontier_js_1.getFinishBlockingFrontier)(state2).map((i) => i.route);
324919
325487
  const reasonText = String(toolArgs.summary || "").replace(/\s+/g, " ").trim().slice(0, 400);
@@ -325035,7 +325603,7 @@ Exploration complete: ${summary}`);
325035
325603
  continue;
325036
325604
  }
325037
325605
  if (toolName === "fill_credential" || toolName === "fill_credential_form") {
325038
- if (emailVerificationUnavailable) {
325606
+ if (emailVerificationUnavailable || inboxState?.authDead) {
325039
325607
  const codeGuard = (0, inbox_tools_js_1.guardVerificationCodeFabrication)(toolName, toolArgs);
325040
325608
  if (codeGuard.blocked) {
325041
325609
  logs2.push(` BLOCKED ${toolName}: verification-code field in email-verification-unavailable mode (field="${codeGuard.matchedField}")`);
@@ -325129,7 +325697,7 @@ Exploration complete: ${summary}`);
325129
325697
  continue;
325130
325698
  }
325131
325699
  }
325132
- if (emailVerificationUnavailable) {
325700
+ if (emailVerificationUnavailable || inboxState?.authDead) {
325133
325701
  const codeGuard = (0, inbox_tools_js_1.guardVerificationCodeFabrication)(toolName, toolArgs);
325134
325702
  if (codeGuard.blocked) {
325135
325703
  logs2.push(` BLOCKED ${toolName}: verification-code field in email-verification-unavailable mode (field="${codeGuard.matchedField}")`);
@@ -326544,7 +327112,10 @@ var require_auth_capture_run = __commonJS({
326544
327112
  exports.getAuthCaptureTimeoutMs = getAuthCaptureTimeoutMs;
326545
327113
  exports.getAuthCaptureRunRetryMaxAttempts = getAuthCaptureRunRetryMaxAttempts;
326546
327114
  exports.classifyCaptureMiss = classifyCaptureMiss;
327115
+ exports.harvestCollapsedAuthProof = harvestCollapsedAuthProof;
327116
+ exports.selectAuthLandingRoute = selectAuthLandingRoute;
326547
327117
  exports.harvestAuthProofLive = harvestAuthProofLive;
327118
+ exports.detectRedirectBackCollapse = detectRedirectBackCollapse;
326548
327119
  exports.findCredentialLiteralLeaks = findCredentialLiteralLeaks;
326549
327120
  exports.runLoginCaptureRunWithOutcome = runLoginCaptureRunWithOutcome;
326550
327121
  exports.runLoginCaptureRun = runLoginCaptureRun;
@@ -326665,35 +327236,26 @@ ${keyLines || "- (none provided)"}
326665
327236
  return false;
326666
327237
  return true;
326667
327238
  }
326668
- function harvestAuthProofLive(siteMap, redirectRoute, loginRoute, credentials, logs2) {
326669
- const LOGOUT_RE = /\b(log\s?out|sign\s?out)\b/i;
326670
- const ACCOUNT_RE = /\b(account|profile|settings|avatar|notifications?|dashboard|inbox|my\s+account|user\s+menu)\b/i;
326671
- const GREETING_RE = /\b(welcome|hello|hey|good\s+(morning|afternoon|evening|day))\b/i;
326672
- const AUTH_ROUTE_RE = /\/(log[\s_-]?in|sign[\s_-]?in|sign[\s_-]?up|register|auth|forgot|reset)\b/i;
326673
- const isLoginPage = (route2) => loginRoute != null && route2 === loginRoute || AUTH_ROUTE_RE.test(route2 ?? "");
326674
- const authedPages = siteMap.pages.filter((p) => (p.requiresAuth || redirectRoute !== null && p.route === redirectRoute) && !isLoginPage(p.route));
326675
- const landing = (redirectRoute !== null ? authedPages.find((p) => p.route === redirectRoute) : void 0) ?? authedPages[0] ?? null;
326676
- if (!landing) {
326677
- logs2(" \u26A0 auth-proof: no authenticated landing page captured \u2014 cannot derive a durable proof");
326678
- return null;
326679
- }
326680
- const loginRoleNames = /* @__PURE__ */ new Set();
326681
- const loginTestIds = /* @__PURE__ */ new Set();
326682
- for (const p of siteMap.pages) {
326683
- if (!isLoginPage(p.route) || p.route === landing.route)
326684
- continue;
326685
- for (const el of p.elements ?? []) {
326686
- const nm = (el.label ?? "").trim().toLowerCase();
326687
- if (el.role && nm)
326688
- loginRoleNames.add(`${el.role}|${nm}`);
326689
- if (el.testId)
326690
- loginTestIds.add(el.testId);
326691
- }
326692
- }
327239
+ var LOGOUT_RE = /\b(log\s?out|sign\s?out)\b/i;
327240
+ var ACCOUNT_RE = /\b(account|profile|settings|avatar|notifications?|dashboard|inbox|my\s+account|user\s+menu)\b/i;
327241
+ var GREETING_RE = /\b(welcome|hello|hey|good\s+(morning|afternoon|evening|day))\b/i;
327242
+ function describeAuthProof(proof) {
327243
+ switch (proof.kind) {
327244
+ case "role":
327245
+ return `role:${proof.role}${proof.name ? `(${proof.name})` : ""}`;
327246
+ case "testid":
327247
+ return `testid:${proof.testId}`;
327248
+ case "endpoint":
327249
+ return `endpoint:${proof.url}`;
327250
+ case "url":
327251
+ return `url:${proof.route}`;
327252
+ }
327253
+ }
327254
+ function rankDurableAuthProof(landingElements, loginRoleNames, loginTestIds, credentials, logs2) {
326693
327255
  const haveLoginDiff = loginRoleNames.size > 0 || loginTestIds.size > 0;
326694
327256
  const roleNameFreq = /* @__PURE__ */ new Map();
326695
327257
  const testIdFreq = /* @__PURE__ */ new Map();
326696
- for (const el of landing.elements ?? []) {
327258
+ for (const el of landingElements) {
326697
327259
  const nm = (el.label ?? "").trim().toLowerCase();
326698
327260
  if (el.role && nm)
326699
327261
  roleNameFreq.set(`${el.role}|${nm}`, (roleNameFreq.get(`${el.role}|${nm}`) ?? 0) + 1);
@@ -326733,7 +327295,7 @@ ${keyLines || "- (none provided)"}
326733
327295
  return true;
326734
327296
  };
326735
327297
  const ranked = [];
326736
- for (const el of landing.elements ?? []) {
327298
+ for (const el of landingElements) {
326737
327299
  const label = (el.label ?? "").trim();
326738
327300
  const role = el.role ?? "";
326739
327301
  const testId = el.testId ?? "";
@@ -326745,7 +327307,7 @@ ${keyLines || "- (none provided)"}
326745
327307
  const r = role === "link" ? "link" : "button";
326746
327308
  ranked.push({ tier: 1, proof: { kind: "role", role: r, name: label }, desc: `${r} "${label}" (logout control)` });
326747
327309
  }
326748
- if (label && ACCOUNT_RE.test(label) && !personalized && uniqueRoleName && (role === "button" || role === "link") && absentOnLogin(roleNameKey, "")) {
327310
+ if (label && ACCOUNT_RE.test(label) && !personalized && uniqueRoleName && haveLoginDiff && (role === "button" || role === "link") && absentOnLogin(roleNameKey, "")) {
326749
327311
  ranked.push({ tier: 2, proof: { kind: "role", role, name: label }, desc: `${role} "${label}" (account control)` });
326750
327312
  }
326751
327313
  if (testId && isSemanticTestId(testId) && uniqueTestId && haveLoginDiff && absentOnLogin("", testId)) {
@@ -326759,25 +327321,110 @@ ${keyLines || "- (none provided)"}
326759
327321
  }
326760
327322
  }
326761
327323
  if (ranked.length === 0) {
326762
- const loginPathname = loginRoute ? loginRoute.replace(/[?#].*$/, "").replace(/\/+$/, "") : null;
326763
- const toProofPathname = (r) => {
326764
- if (!r)
326765
- return null;
326766
- const p = r.replace(/[?#].*$/, "").replace(/\/+$/, "");
326767
- return p && p !== "/" && !isLoginPage(p) && p !== loginPathname ? p : null;
326768
- };
326769
- const urlRoute = toProofPathname(redirectRoute) ?? toProofPathname(landing.route);
326770
- if (urlRoute) {
326771
- logs2(` \u2713 url auth-proof: authenticated landing route ${urlRoute} (no durable element proof on the default-state page)`);
326772
- return { kind: "url", route: urlRoute };
326773
- }
326774
- logs2(" \u26A0 auth-proof: no durable element proof and no concrete authed landing route \u2014 falling back to model-authored login (no blind navigation default)");
327324
+ logs2(" \u26A0 auth-proof: no durable login-absent element proof on the authenticated page (no url fallback \u2014 coverage deferred to a positive proof)");
326775
327325
  return null;
326776
327326
  }
326777
327327
  ranked.sort((a, b) => a.tier - b.tier);
326778
327328
  logs2(` \u2713 live auth-proof: ${ranked[0].desc}`);
326779
327329
  return ranked[0].proof;
326780
327330
  }
327331
+ function harvestCollapsedAuthProof(landingElements, loginAbsentElements, credentials, logs2) {
327332
+ const loginRoleNames = /* @__PURE__ */ new Set();
327333
+ const loginTestIds = /* @__PURE__ */ new Set();
327334
+ for (const el of loginAbsentElements) {
327335
+ const nm = (el.label ?? "").trim().toLowerCase();
327336
+ if (el.role && nm)
327337
+ loginRoleNames.add(`${el.role}|${nm}`);
327338
+ if (el.testId)
327339
+ loginTestIds.add(el.testId);
327340
+ }
327341
+ return rankDurableAuthProof(landingElements, loginRoleNames, loginTestIds, credentials, logs2);
327342
+ }
327343
+ function pickAuthLandingPage(siteMap, redirectRoute, loginRoute) {
327344
+ const isLoginPage = (route2) => loginRoute != null && route2 === loginRoute || AUTH_ROUTE_RE.test(route2 ?? "");
327345
+ const authedPages = siteMap.pages.filter((p) => (p.requiresAuth || redirectRoute !== null && p.route === redirectRoute) && !isLoginPage(p.route));
327346
+ return (redirectRoute !== null ? authedPages.find((p) => p.route === redirectRoute) : void 0) ?? authedPages[0] ?? null;
327347
+ }
327348
+ function selectAuthLandingRoute(siteMap, redirectRoute, loginRoute) {
327349
+ return pickAuthLandingPage(siteMap, redirectRoute, loginRoute)?.route ?? null;
327350
+ }
327351
+ function harvestAuthProofLive(siteMap, redirectRoute, loginRoute, credentials, logs2) {
327352
+ const isLoginPage = (route2) => loginRoute != null && route2 === loginRoute || AUTH_ROUTE_RE.test(route2 ?? "");
327353
+ const landing = pickAuthLandingPage(siteMap, redirectRoute, loginRoute);
327354
+ if (!landing) {
327355
+ logs2(" \u26A0 auth-proof: no authenticated landing page captured \u2014 cannot derive a durable proof");
327356
+ return null;
327357
+ }
327358
+ const loginRoleNames = /* @__PURE__ */ new Set();
327359
+ const loginTestIds = /* @__PURE__ */ new Set();
327360
+ for (const p of siteMap.pages) {
327361
+ if (!isLoginPage(p.route) || p.route === landing.route)
327362
+ continue;
327363
+ for (const el of p.elements ?? []) {
327364
+ const nm = (el.label ?? "").trim().toLowerCase();
327365
+ if (el.role && nm)
327366
+ loginRoleNames.add(`${el.role}|${nm}`);
327367
+ if (el.testId)
327368
+ loginTestIds.add(el.testId);
327369
+ }
327370
+ }
327371
+ return rankDurableAuthProof(landing.elements ?? [], loginRoleNames, loginTestIds, credentials, logs2);
327372
+ }
327373
+ var AUTH_ROUTE_RE = /\/(log[\s_-]?in|sign[\s_-]?in|sign[\s_-]?up|register|auth|forgot|reset)\b/i;
327374
+ var AUTO_NAV_TRIGGERS = /* @__PURE__ */ new Set(["AUTO", "REDIRECT"]);
327375
+ var USER_ACTION_TRIGGERS = /* @__PURE__ */ new Set(["CLICK", "SUBMIT", "NAVIGATION"]);
327376
+ var stripToBarePath = (route2) => {
327377
+ if (!route2)
327378
+ return null;
327379
+ let path = route2;
327380
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(route2)) {
327381
+ try {
327382
+ path = new URL(route2).pathname;
327383
+ } catch {
327384
+ }
327385
+ }
327386
+ const p = path.replace(/[?#].*$/, "").replace(/\/+$/, "");
327387
+ return p || "/";
327388
+ };
327389
+ function detectRedirectBackCollapse(baseUrl, transitions) {
327390
+ let basePath;
327391
+ try {
327392
+ basePath = stripToBarePath(new URL(baseUrl).pathname);
327393
+ } catch {
327394
+ return null;
327395
+ }
327396
+ if (!basePath || basePath === "/" || AUTH_ROUTE_RE.test(basePath))
327397
+ return null;
327398
+ const userActionEdges = new Set(transitions.filter((t) => USER_ACTION_TRIGGERS.has(t.triggerType)).map((t) => `${stripToBarePath(t.fromRoute) ?? ""} ${stripToBarePath(t.toRoute) ?? ""}`));
327399
+ const bounceIdx = transitions.findIndex((t) => AUTO_NAV_TRIGGERS.has(t.triggerType) && stripToBarePath(t.fromRoute) === basePath && AUTH_ROUTE_RE.test(stripToBarePath(t.toRoute) ?? "") && !userActionEdges.has(`${basePath} ${stripToBarePath(t.toRoute) ?? ""}`));
327400
+ if (bounceIdx < 0)
327401
+ return null;
327402
+ const authRoute = stripToBarePath(transitions[bounceIdx].toRoute);
327403
+ if (!authRoute)
327404
+ return null;
327405
+ const returned = transitions.slice(bounceIdx + 1).some((t) => AUTH_ROUTE_RE.test(stripToBarePath(t.fromRoute) ?? "") && stripToBarePath(t.toRoute) === basePath);
327406
+ if (!returned)
327407
+ return null;
327408
+ return { basePath, authRoute };
327409
+ }
327410
+ function pickRouteElements(routeElements, wantRoute) {
327411
+ const want = stripToBarePath(wantRoute);
327412
+ for (const [route2, els] of routeElements) {
327413
+ if (stripToBarePath(route2) === want && (els?.length ?? 0) > 0)
327414
+ return els;
327415
+ }
327416
+ return [];
327417
+ }
327418
+ function pickLoginFormElements(routeElements, authRoute) {
327419
+ const exact = pickRouteElements(routeElements, authRoute);
327420
+ if (exact.length > 0)
327421
+ return exact;
327422
+ for (const [route2, els] of routeElements) {
327423
+ if (AUTH_ROUTE_RE.test(stripToBarePath(route2) ?? "") && (els?.length ?? 0) > 0)
327424
+ return els;
327425
+ }
327426
+ return [];
327427
+ }
326781
327428
  function findCredentialLiteralLeaks(rendered, credentials) {
326782
327429
  const leaked = [];
326783
327430
  for (const [key, value] of Object.entries(credentials)) {
@@ -326790,12 +327437,22 @@ ${keyLines || "- (none provided)"}
326790
327437
  return leaked;
326791
327438
  }
326792
327439
  async function verifyAuthRecipe(recipe, baseUrl, credentials, logs2) {
327440
+ const baselineRoute = recipe.redirectRoute ?? baseUrl;
326793
327441
  const spec = [
326794
327442
  `import { test, expect } from '@playwright/test';`,
326795
327443
  ``,
326796
327444
  (0, auth_recipe_js_1.renderAuthFn)(recipe),
326797
327445
  ``,
326798
327446
  `test('__vqa_auth_verify', async ({ page }) => {`,
327447
+ ` // DIFFERENTIAL BASELINE (logged out): the durable proof must NOT already hold here, else it`,
327448
+ ` // does not discriminate an authenticated session from an anonymous one \u2014 reject rather than`,
327449
+ ` // publish a proof that passes while logged out (the cardinal invariant). The goto is NOT`,
327450
+ ` // .catch-swallowed: a genuine navigation failure must fail the verify (fail-closed) rather than`,
327451
+ ` // leave the baseline running on about:blank where the proof is vacuously "absent". An auth-gated`,
327452
+ ` // route resolves fine (it follows the redirect to /login).`,
327453
+ ` await page.goto(${JSON.stringify(baselineRoute)}, { waitUntil: 'domcontentloaded' });`,
327454
+ ` ${(0, auth_recipe_js_1.renderProofBaselineCheck)(recipe.authProof, " ")}`,
327455
+ ` // AUTHED: real login + the durable proof assertion (inside __vqaLogin).`,
326799
327456
  ` await __vqaLogin(page);`,
326800
327457
  `});`,
326801
327458
  ``
@@ -326897,21 +327554,50 @@ ${keyLines || "- (none provided)"}
326897
327554
  cmds: login.cmds.map((c) => (0, scrub_credentials_js_1.scrubCredentials)(c, credentials)),
326898
327555
  preNavCmds: login.preNavCmds?.map((c) => (0, scrub_credentials_js_1.scrubCredentials)(c, credentials))
326899
327556
  };
326900
- const authProof = harvestAuthProofLive(loginSiteMap, scrubbedLogin.redirectRoute, scrubbedLogin.route, credentials, logs2);
327557
+ let authProof = harvestAuthProofLive(loginSiteMap, scrubbedLogin.redirectRoute, scrubbedLogin.route, credentials, logs2);
327558
+ let effectiveLogin = scrubbedLogin;
327559
+ if (authProof && (authProof.kind === "role" || authProof.kind === "testid")) {
327560
+ const landingRoute = selectAuthLandingRoute(loginSiteMap, scrubbedLogin.redirectRoute, scrubbedLogin.route);
327561
+ if (landingRoute)
327562
+ effectiveLogin = { ...effectiveLogin, redirectRoute: landingRoute };
327563
+ }
326901
327564
  if (!authProof) {
326902
- logs2(" \u26A0 auth unverified (no durable signed-in proof on the authenticated page) \u2014 falling back to model-authored login");
326903
- return { recipe: null, outcome: exploreMiss("verify_failed") };
327565
+ const collapse = detectRedirectBackCollapse(baseUrl, exploreResult.collectedTransitions);
327566
+ if (collapse) {
327567
+ const landingEls = pickRouteElements(handle.routeElements, collapse.basePath);
327568
+ const loginEls = pickLoginFormElements(handle.routeElements, collapse.authRoute);
327569
+ const collapsedProof = harvestCollapsedAuthProof(landingEls, loginEls, credentials, logs2);
327570
+ if (collapsedProof) {
327571
+ logs2(` \u2713 collapsed-entry element auth-proof (redirect-back-to-entry): ${JSON.stringify(collapsedProof)} on ${collapse.basePath} (${landingEls.length} authed / ${loginEls.length} login-form elements differenced)`);
327572
+ authProof = collapsedProof;
327573
+ effectiveLogin = { ...effectiveLogin, redirectRoute: collapse.basePath };
327574
+ } else {
327575
+ logs2(" \u26A0 redirect-back-to-entry collapse detected but no durable authed-only element differenced (no url fallback) \u2014 deferring to model-authored login");
327576
+ }
327577
+ }
326904
327578
  }
326905
- const candidate = { ...(0, auth_recipe_js_1.buildAuthRecipe)(scrubbedLogin, authProof, (/* @__PURE__ */ new Date()).toISOString()), appOrigin: loginRedirectAppOrigin };
326906
- const rendered = (0, auth_recipe_js_1.renderAuthFn)(candidate);
326907
- const leaks = findCredentialLiteralLeaks(rendered, credentials);
326908
- if (leaks.length > 0) {
326909
- logs2(` \u26A0 auth recipe rejected: rendered __vqaLogin contains literal credential value(s) for ${leaks.join(", ")} \u2014 falling back to model-authored login`);
326910
- return { recipe: null, outcome: "credential_leak" };
327579
+ let identityEndpointProof = null;
327580
+ try {
327581
+ const cookies = await handle.page.context().cookies();
327582
+ if (cookies.some((c) => /(next-auth|authjs)\.session-token/.test(c.name))) {
327583
+ identityEndpointProof = { kind: "endpoint", url: "/api/auth/session" };
327584
+ logs2(" \u2139 NextAuth/Auth.js session cookie detected \u2014 trying an identity-endpoint proof (/api/auth/session) before the element proof");
327585
+ }
327586
+ } catch {
327587
+ }
327588
+ const proofCandidates = [];
327589
+ if (identityEndpointProof)
327590
+ proofCandidates.push(identityEndpointProof);
327591
+ if (authProof)
327592
+ proofCandidates.push(authProof);
327593
+ if (proofCandidates.length === 0) {
327594
+ logs2(" \u26A0 auth unverified (no positive proof \u2014 no identity endpoint, no login-absent element) \u2014 falling back to model-authored login");
327595
+ return { recipe: null, outcome: exploreMiss("verify_failed") };
326911
327596
  }
326912
- const planLeaks = findCredentialLiteralLeaks(JSON.stringify(candidate.replayPlan ?? []), credentials);
326913
- if (planLeaks.length > 0) {
326914
- logs2(` \u26A0 auth recipe rejected: structured replayPlan contains literal credential value(s) for ${planLeaks.join(", ")} \u2014 falling back to model-authored login`);
327597
+ const loginText = [...effectiveLogin.cmds, ...effectiveLogin.preNavCmds ?? []].join("\n") + "\n" + JSON.stringify(effectiveLogin.replayPlan ?? []);
327598
+ const loginLeaks = findCredentialLiteralLeaks(loginText, credentials);
327599
+ if (loginLeaks.length > 0) {
327600
+ logs2(` \u26A0 auth recipe rejected: login sequence contains literal credential value(s) for ${loginLeaks.join(", ")} \u2014 falling back to model-authored login`);
326915
327601
  return { recipe: null, outcome: "credential_leak" };
326916
327602
  }
326917
327603
  try {
@@ -326919,14 +327605,25 @@ ${keyLines || "- (none provided)"}
326919
327605
  } catch {
326920
327606
  }
326921
327607
  handle = null;
326922
- const ok = await verifyAuthRecipe(candidate, baseUrl, credentials, logs2);
326923
- if (!ok) {
326924
- logs2(" \u26A0 auth unverified (live-verify replay failed \u2014 fleet-config drift) \u2014 falling back to model-authored login");
326925
- return { recipe: null, outcome: exploreMiss("verify_failed") };
327608
+ let lastReject = "no candidate verified";
327609
+ for (const proof of proofCandidates) {
327610
+ const candidate = { ...(0, auth_recipe_js_1.buildAuthRecipe)(effectiveLogin, proof, (/* @__PURE__ */ new Date()).toISOString()), appOrigin: loginRedirectAppOrigin };
327611
+ const renderLeaks = findCredentialLiteralLeaks((0, auth_recipe_js_1.renderAuthFn)(candidate), credentials);
327612
+ if (renderLeaks.length > 0) {
327613
+ lastReject = `${describeAuthProof(proof)} render coincidentally matched credential value(s) ${renderLeaks.join(", ")}`;
327614
+ logs2(` \u26A0 auth-proof candidate skipped (${lastReject}) \u2014 trying the next candidate`);
327615
+ continue;
327616
+ }
327617
+ const ok = await verifyAuthRecipe(candidate, baseUrl, credentials, logs2);
327618
+ if (ok) {
327619
+ logs2(` \u2713 auth recipe verified (hash ${candidate.recipeHash}, proof ${describeAuthProof(proof)}, ${(candidate.replayPlan ?? []).length} replay steps) \u2014 stamping into @needs-auth tests`);
327620
+ return { recipe: candidate, outcome: "captured" };
327621
+ }
327622
+ lastReject = `${describeAuthProof(proof)} failed the differential verify`;
327623
+ logs2(` \u26A0 auth-proof candidate rejected (${lastReject}) \u2014 trying the next candidate`);
326926
327624
  }
326927
- const proofDesc = authProof.kind === "role" ? `role:${authProof.role}${authProof.name ? `(${authProof.name})` : ""}` : authProof.kind === "testid" ? `testid:${authProof.testId}` : `url:${authProof.route}`;
326928
- logs2(` \u2713 auth recipe verified (hash ${candidate.recipeHash}, proof ${proofDesc}, ${(candidate.replayPlan ?? []).length} replay steps) \u2014 stamping into @needs-auth tests`);
326929
- return { recipe: candidate, outcome: "captured" };
327625
+ logs2(` \u26A0 auth unverified (all proof candidates failed: ${lastReject}) \u2014 falling back to model-authored login`);
327626
+ return { recipe: null, outcome: exploreMiss("verify_failed") };
326930
327627
  } catch (err) {
326931
327628
  logs2(` \u26A0 auth-capture run failed: ${err instanceof Error ? err.message : String(err)} \u2014 falling back to model-authored login`);
326932
327629
  return { recipe: null, outcome: (0, ai_retry_js_1.isTransientRateError)(err) ? "rate_limited" : "error" };
@@ -334053,8 +334750,13 @@ Respond with ONLY valid JSON:
334053
334750
  if (!(0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) && ctx.mode !== "capture")
334054
334751
  return null;
334055
334752
  if (ctx.authRecipe) {
334056
- pushLog(`auth-prologue: reusing persisted login recipe ${ctx.authRecipe.recipeHash} \u2014 skipping capture`);
334057
- return Promise.resolve(ctx.authRecipe);
334753
+ if (ctx.authRecipe.authProof?.kind === "url") {
334754
+ pushLog(`auth-prologue: persisted recipe ${ctx.authRecipe.recipeHash} uses a demoted url proof \u2014 re-capturing with the differential pipeline (legacy invalidation)`);
334755
+ ctx.authRecipe = void 0;
334756
+ } else {
334757
+ pushLog(`auth-prologue: reusing persisted login recipe ${ctx.authRecipe.recipeHash} \u2014 skipping capture`);
334758
+ return Promise.resolve(ctx.authRecipe);
334759
+ }
334058
334760
  }
334059
334761
  const exploreModel = ctx.exploreModel ?? "";
334060
334762
  const authCaptureModel = ctx.authCaptureModel ?? "";
@@ -349601,7 +350303,7 @@ var require_package4 = __commonJS({
349601
350303
  "package.json"(exports, module) {
349602
350304
  module.exports = {
349603
350305
  name: "@validate.qa/runner",
349604
- version: "1.0.56",
350306
+ version: "1.0.57",
349605
350307
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
349606
350308
  bin: {
349607
350309
  "validate-runner": "dist/cli.js"