@resolveio/server-lib 22.3.235 → 22.3.237

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.
@@ -1,33 +1,4 @@
1
1
  "use strict";
2
- var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
3
- if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
4
- return cooked;
5
- };
6
- var __read = (this && this.__read) || function (o, n) {
7
- var m = typeof Symbol === "function" && o[Symbol.iterator];
8
- if (!m) return o;
9
- var i = m.call(o), r, ar = [], e;
10
- try {
11
- while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
12
- }
13
- catch (error) { e = { error: error }; }
14
- finally {
15
- try {
16
- if (r && !r.done && (m = i["return"])) m.call(i);
17
- }
18
- finally { if (e) throw e.error; }
19
- }
20
- return ar;
21
- };
22
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
23
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
24
- if (ar || !(i in from)) {
25
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
26
- ar[i] = from[i];
27
- }
28
- }
29
- return to.concat(ar || Array.prototype.slice.call(from));
30
- };
31
2
  Object.defineProperty(exports, "__esModule", { value: true });
32
3
  exports.buildResolveIORunnerQaEnvScript = buildResolveIORunnerQaEnvScript;
33
4
  exports.buildResolveIORunnerQaPreflightScript = buildResolveIORunnerQaPreflightScript;
@@ -107,6 +78,9 @@ function buildResolveIORunnerQaEnvScript(options) {
107
78
  'resolveio_support_codex_lane_is_build() {',
108
79
  ' case "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" in build|support_build|support-build) return 0 ;; *) return 1 ;; esac',
109
80
  '}',
81
+ 'resolveio_support_codex_lane_is_qa() {',
82
+ ' case "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" in qa|support_qa|support-qa) return 0 ;; *) return 1 ;; esac',
83
+ '}',
110
84
  'resolveio_support_codex_block_build_lane_qa() {',
111
85
  ' resolveio_support_codex_lane_is_build || return 0',
112
86
  ' echo "ResolveIO support lane guard: build lane cannot run local QA/browser/server/client commands; return a build fix and let the QA lane run browser validation." >&2',
@@ -180,6 +154,16 @@ function buildResolveIORunnerQaEnvScript(options) {
180
154
  ' esac',
181
155
  ' fi',
182
156
  'fi',
157
+ 'if [ "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" = "qa" ] || [ "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" = "support_qa" ] || [ "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" = "support-qa" ]; then',
158
+ ' if [ "${1:-}" = "run" ]; then',
159
+ ' case "${2:-}" in',
160
+ ' build|build-dev|build-prod|server|client|start|dev|serve|local-qa)',
161
+ ' echo "ResolveIO support QA lane guard: refusing npm run ${2:-} from the QA proof lane. Use the already-started localhost harness and bounded node/Puppeteer/Mongo proof scripts; return a blocked AIQaBusinessAssertion with the exact error if proof cannot run." >&2',
162
+ ' exit 86',
163
+ ' ;;',
164
+ ' esac',
165
+ ' fi',
166
+ 'fi',
183
167
  'is_angular_build=0',
184
168
  'if [ "${1:-}" = "run" ]; then',
185
169
  ' case "${2:-}" in',
@@ -1276,7 +1260,7 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
1276
1260
  'const crypto = require("crypto");',
1277
1261
  '',
1278
1262
  'if (isBuildLane()) {',
1279
- ' console.error("ResolveIO support lane guard: live-data seeding is owned by the QA lane, not the 5.3 build lane.");',
1263
+ ' console.error("ResolveIO support lane guard: live-data seeding is owned by the QA lane, not the build lane.");',
1280
1264
  ' process.exit(86);',
1281
1265
  '}',
1282
1266
  '',
@@ -1288,7 +1272,6 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
1288
1272
  '',
1289
1273
  'function writeResult(payload, exitCode = 0) {',
1290
1274
  ' const full = { ...payload, created_at: new Date().toISOString() };',
1291
- ' normalizeCoverageMatrixFromLiveSeed(full);',
1292
1275
  ' fs.writeFileSync(resultPath, JSON.stringify(full, null, 2));',
1293
1276
  ' console.log(JSON.stringify(full, null, 2));',
1294
1277
  ' process.exit(exitCode);',
@@ -1313,65 +1296,14 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
1313
1296
  ' try { return JSON.parse(fs.readFileSync(filePath, "utf8")); } catch (error) { return null; }',
1314
1297
  '}',
1315
1298
  '',
1316
- 'function normalizeCoverageMatrixFromLiveSeed(payload) {',
1317
- ' const bolContext = payload && payload.selected && payload.selected.truck_treating_bol_context;',
1318
- ' const notes = Array.isArray(payload && payload.notes) ? payload.notes.map((note) => String(note || "")) : [];',
1319
- ' const billingDisabled = notes.some((note) => /billing fixtures are disabled/i.test(note));',
1320
- ' if (!bolContext || !billingDisabled) return;',
1321
- ' const matrixPath = path.join(artifactDir, "qa-coverage-matrix.json");',
1322
- ' const matrix = readJsonIfExists(matrixPath) || { status: "started", seeded: true, source: "qa-live-data-seed" };',
1323
- ' const browserRoutes = bolContext.browser_routes && typeof bolContext.browser_routes === "object" ? bolContext.browser_routes : {};',
1324
- ' const routeHints = Array.isArray(bolContext.route_name_hints) ? bolContext.route_name_hints.map((value) => String(value || "").trim()).filter(Boolean) : [];',
1325
- ' const linkedRoutes = Array.isArray(bolContext.linked_truck_treating_routes) ? bolContext.linked_truck_treating_routes.map((value) => String(value || "").trim()).filter(Boolean) : [];',
1326
- ' const bolIds = Array.isArray(bolContext.bol_ids) ? bolContext.bol_ids.map((value) => String(value || "").trim()).filter(Boolean) : [];',
1327
- ' const routeIds = Array.isArray(bolContext.route_ids) ? bolContext.route_ids.map((value) => String(value || "").trim()).filter(Boolean) : [];',
1328
- ' const truckTreatingRouteIds = Array.isArray(bolContext.truck_treating_route_ids) ? bolContext.truck_treating_route_ids.map((value) => String(value || "").trim()).filter(Boolean) : [];',
1329
- ' const primaryStatus = String(bolContext.primary_bol_status || "");',
1330
- ' const delivered = Boolean(bolContext.primary_bol_delivered) || /delivered/i.test(primaryStatus);',
1331
- ' const hintedBol = routeHints.join(" ");',
1332
- ' const bolNumber = (/\\bBOL\\s*#?\\s*(\\d+)\\b/i.exec(hintedBol) || [])[1];',
1333
- ' const bolLabel = bolNumber ? `BOL ${bolNumber}` : (bolIds[0] ? `BOL ${bolIds[0]}` : "the requested BOL");',
1334
- ' const routeHint = routeHints.find((hint) => !/\\bBOL\\b/i.test(hint) && !/^\\d+$/.test(hint)) || "the ticket route hint";',
1335
- ' const routeSummary = linkedRoutes.length ? linkedRoutes.join(", ") : (truckTreatingRouteIds.length ? truckTreatingRouteIds.join(", ") : routeIds.slice(0, 6).join(", "));',
1336
- ' const deliveredNote = delivered ? ` The live BOL status is ${primaryStatus || "Delivered"}, so the direct delivery route is an action route that may correctly redirect; QA must use delivered Open File/detail proof or a localhost-only deliverable fixture copied from this live context for Treat/Deliver actions.` : "";',
1337
- ' const liveAssertion = `Live Mongo seeded ${bolLabel} and proved ${routeHint} is not a linked truck-treating route for that BOL; QA must continue against linked live route data: ${routeSummary}.${deliveredNote}`;',
1338
- ' matrix.status = "in_progress";',
1339
- ' matrix.source = "qa-live-data-seed-live-bol-context";',
1340
- ' matrix.live_seed_context_artifact = "qa-artifacts/qa-live-data-seed-result.json";',
1341
- ' matrix.billing_rows_removed_reason = "Live data seed disabled billing fixtures for this ticket; invoice/billing matrix rows do not apply to truck-treating BOL QA.";',
1342
- ' matrix.rows = [',
1343
- ' { workflow: "Truck treating BOL live-data target and route validation", route: browserRoutes.delivery || browserRoutes.detail || browserRoutes.list || "Deliver BOL live seeded BOL context", assertion: liveAssertion, required_proof: "Live Mongo seed artifact showing BOL id/status, linked route ids, ticket route-hint match result, and browser route.", status: "pass", result: "pass", screenshot: "", caption: `Live data seeded ${bolLabel}; the ticket route label did not match the BOL-linked live routes, so QA continued against ${routeSummary}.`, artifact: "qa-artifacts/qa-live-data-seed-result.json", persisted_assertion: `qa-live-data-seed-result.json route_hint_matched=${bolContext.route_hint_matched}; bol_ids=${bolIds.join(",") || "none"}; route_ids=${routeIds.length}; truck_treating_route_ids=${truckTreatingRouteIds.length}; primary_bol_status=${primaryStatus || "unknown"}.` },',
1344
- ' { workflow: "Delivered BOL detail/Open File proof", route: browserRoutes.detail || browserRoutes.list || browserRoutes.delivery || "/", assertion: `Open the seeded ${bolLabel} in the truck-treating delivered/detail workflow and prove the local QA database loaded the customer-facing BOL screen without relying on the already-delivered action route.`, required_proof: "Customer-facing screenshot of the BOL detail/list/Open File screen plus BOL id/status assertion from local Mongo.", status: "pending", screenshot: "", caption: "" },',
1345
- ' { workflow: "Deliver BOL service/flush rows do not render as missing chemicals", route: browserRoutes.delivery || browserRoutes.detail || "/", assertion: "Service/flush/non-chemical treatment plans must not be injected into the Deliver BOL chemical list as red missing chemical placeholders; chemical treatment rows still appear normally.", required_proof: "Customer-facing screenshot of the Deliver BOL/detail workflow or a localhost-only deliverable fixture copied from the seeded live BOL context, plus DOM/local Mongo assertion that missing placeholders are limited to Chemical treatment plans.", status: "pending", screenshot: "", caption: "" }',
1346
- ' ];',
1347
- ' fs.writeFileSync(matrixPath, JSON.stringify(matrix, null, 2));',
1348
- '}',
1299
+ 'function desiredSeedProfile() { return "live_context"; }',
1349
1300
  '',
1350
1301
  'function preserveExistingSeedResult(reason) {',
1351
1302
  ' const existing = readJsonIfExists(resultPath);',
1352
1303
  ' const status = String(existing && existing.status || "").toLowerCase();',
1353
1304
  ' const desiredProfile = desiredSeedProfile();',
1354
1305
  ' if (existing && existing.profile && existing.profile !== desiredProfile) return;',
1355
- ' if (existing && existing.profile === "billing_inventory" && shouldSeedBillingDashboardFixtures() && !(existing.selected && existing.selected.qa_billing_fixture)) {',
1356
- ' return;',
1357
- ' }',
1358
- ' if (existing && extractBolIdentifiers().length && !(existing.selected && existing.selected.truck_treating_bol_context)) {',
1359
- ' return;',
1360
- ' }',
1361
- ' if (existing && extractBolIdentifiers().length && existing.selected && existing.selected.truck_treating_bol_context && !(existing.selected.truck_treating_bol_context.browser_routes && existing.selected.truck_treating_bol_context.browser_routes.delivery)) {',
1362
- ' return;',
1363
- ' }',
1364
- ' if (existing && extractBolIdentifiers().length && existing.selected && existing.selected.truck_treating_bol_context && !Object.prototype.hasOwnProperty.call(existing.selected.truck_treating_bol_context, "primary_bol_status")) {',
1365
- ' return;',
1366
- ' }',
1367
- ' if (existing && shouldAutoDiscoverAssetContext() && !(existing.selected && existing.selected.qa_asset_context)) {',
1368
- ' return;',
1369
- ' }',
1370
- ' if (existing && shouldAutoDiscoverProductionInterchangeablesContext() && !(existing.selected && existing.selected.qa_production_interchangeables_context)) {',
1371
- ' return;',
1372
- ' }',
1373
1306
  ' if (["pass", "needs-data"].includes(status)) {',
1374
- ' normalizeCoverageMatrixFromLiveSeed(existing);',
1375
1307
  ' const preserved = { ...existing, reused_existing: true, reuse_reason: reason, checked_at: new Date().toISOString() };',
1376
1308
  ' console.log(JSON.stringify(preserved, null, 2));',
1377
1309
  ' process.exit(0);',
@@ -1379,42 +1311,18 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
1379
1311
  '}',
1380
1312
  '',
1381
1313
  'function resolveRuntimeSource() {',
1382
- ' const explicitRuntimePaths = [',
1383
- ' process.env.RESOLVEIO_QA_MONGO_RUNTIME_PATH,',
1384
- ' process.env.RESOLVEIO_SUPPORT_QA_MONGO_RUNTIME_PATH,',
1385
- ' process.env.RESOLVEIO_RUNNER_QA_MONGO_RUNTIME_PATH',
1386
- ' ].map((value) => String(value || "").trim()).filter(Boolean);',
1387
- ' const candidates = [',
1388
- ' ...explicitRuntimePaths,',
1389
- ' path.join(repoRoot, "mongo-context", ".mongo-runtime.json"),',
1390
- ' path.join(projectRoot, "mongo-context", ".mongo-runtime.json"),',
1391
- ' path.join(repoRoot, ".resolveio-support-context", "mongo-context", ".mongo-runtime.json"),',
1392
- ' path.join(projectRoot, ".resolveio-support-context", "mongo-context", ".mongo-runtime.json"),',
1393
- ' path.join(repoRoot, ".resolveio-context", "mongo-context", ".mongo-runtime.json"),',
1394
- ' path.join(projectRoot, ".resolveio-context", "mongo-context", ".mongo-runtime.json")',
1395
- ' ];',
1396
- ' for (const candidate of candidates) {',
1397
- ' const parsed = readJsonIfExists(candidate);',
1398
- ' if (parsed && parsed.mongo_uri) return { uri: String(parsed.mongo_uri || ""), database: String(parsed.mongo_db || "") };',
1399
- ' }',
1314
+ ' const explicitRuntimePaths = [process.env.RESOLVEIO_QA_MONGO_RUNTIME_PATH, process.env.RESOLVEIO_SUPPORT_QA_MONGO_RUNTIME_PATH, process.env.RESOLVEIO_RUNNER_QA_MONGO_RUNTIME_PATH].map((value) => String(value || "").trim()).filter(Boolean);',
1315
+ ' const candidates = [...explicitRuntimePaths, path.join(repoRoot, "mongo-context", ".mongo-runtime.json"), path.join(projectRoot, "mongo-context", ".mongo-runtime.json"), path.join(repoRoot, ".resolveio-support-context", "mongo-context", ".mongo-runtime.json"), path.join(projectRoot, ".resolveio-support-context", "mongo-context", ".mongo-runtime.json"), path.join(repoRoot, ".resolveio-context", "mongo-context", ".mongo-runtime.json"), path.join(projectRoot, ".resolveio-context", "mongo-context", ".mongo-runtime.json")];',
1316
+ ' for (const candidate of candidates) { const parsed = readJsonIfExists(candidate); if (parsed && parsed.mongo_uri) return { uri: String(parsed.mongo_uri || ""), database: String(parsed.mongo_db || "") }; }',
1400
1317
  ' const envUri = process.env.RESOLVEIO_QA_LIVE_MONGO_URL || process.env.RESOLVEIO_SUPPORT_QA_LIVE_MONGO_URL || process.env.RESOLVEIO_RUNNER_QA_LIVE_MONGO_URL || "";',
1401
1318
  ' const envDb = process.env.RESOLVEIO_QA_LIVE_MONGO_DB || process.env.RESOLVEIO_SUPPORT_QA_LIVE_MONGO_DB || process.env.RESOLVEIO_RUNNER_QA_LIVE_MONGO_DB || "";',
1402
1319
  ' if (envUri) return { uri: envUri, database: envDb };',
1403
1320
  ' return { uri: "", database: "" };',
1404
1321
  '}',
1405
1322
  '',
1406
- 'function redactUri(uri) {',
1407
- ' return String(uri || "").replace(/(mongodb(?:\\+srv)?:\\/\\/)([^:@/?#]+):([^@/?#]+)@/i, "$1$2:***@");',
1408
- '}',
1409
- '',
1410
- 'function isLocalMongoUri(uri) {',
1411
- ' return /mongodb(?:\\+srv)?:\\/\\/(?:[^@/]+@)?(?:127\\.0\\.0\\.1|localhost)(?::\\d+)?\\//i.test(String(uri || ""));',
1412
- '}',
1413
- '',
1414
- 'function isLiveDataRequired() {',
1415
- ' return /^(true|1|yes|on)$/i.test(String(process.env.RESOLVEIO_QA_LIVE_DATA_REQUIRED || process.env.RESOLVEIO_SUPPORT_QA_LIVE_DATA_REQUIRED || process.env.RESOLVEIO_RUNNER_QA_LIVE_DATA_REQUIRED || ""));',
1416
- '}',
1417
- '',
1323
+ 'function redactUri(uri) { return String(uri || "").replace(/(mongodb(?:\\+srv)?:\\/\\/)([^:@/?#]+):([^@/?#]+)@/i, "$1$2:***@"); }',
1324
+ 'function isLocalMongoUri(uri) { return /mongodb(?:\\+srv)?:\\/\\/(?:[^@/]+@)?(?:127\\.0\\.0\\.1|localhost)(?::\\d+)?\\//i.test(String(uri || "")); }',
1325
+ 'function isLiveDataRequired() { return /^(true|1|yes|on)$/i.test(String(process.env.RESOLVEIO_QA_LIVE_DATA_REQUIRED || process.env.RESOLVEIO_SUPPORT_QA_LIVE_DATA_REQUIRED || process.env.RESOLVEIO_RUNNER_QA_LIVE_DATA_REQUIRED || "")); }',
1418
1326
  'function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }',
1419
1327
  '',
1420
1328
  'async function waitForTargetMongo(MongoClient, targetUri) {',
@@ -1422,1133 +1330,85 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
1422
1330
  ' let lastError = null;',
1423
1331
  ' while (Date.now() < deadline) {',
1424
1332
  ' const client = new MongoClient(targetUri, { serverSelectionTimeoutMS: 2500 });',
1425
- ' try {',
1426
- ' await client.connect();',
1427
- ' const admin = client.db("admin");',
1428
- ' await admin.command({ ping: 1 });',
1429
- ' let hello = null;',
1430
- ' try { hello = await admin.command({ hello: 1 }); } catch (error) {',
1431
- ' try { hello = await admin.command({ isMaster: 1 }); } catch (inner) { hello = null; }',
1432
- ' }',
1433
- ' const writable = !hello || hello.isWritablePrimary === true || hello.ismaster === true || !hello.setName;',
1434
- ' if (!writable) {',
1435
- ' lastError = new Error(`target_local_mongo_not_primary: ${hello && (hello.primary || hello.me || hello.setName) || "unknown"}`);',
1436
- ' await client.close().catch(() => undefined);',
1437
- ' await delay(1500);',
1438
- ' continue;',
1439
- ' }',
1440
- ' await client.close().catch(() => undefined);',
1441
- ' return;',
1442
- ' } catch (error) {',
1443
- ' lastError = error;',
1444
- ' await client.close().catch(() => undefined);',
1445
- ' await delay(1500);',
1446
- ' }',
1333
+ ' try { await client.connect(); await client.db("admin").command({ ping: 1 }); await client.close().catch(() => undefined); return; }',
1334
+ ' catch (error) { lastError = error; await client.close().catch(() => undefined); await delay(1500); }',
1447
1335
  ' }',
1448
1336
  ' const detail = lastError && (lastError.message || String(lastError)) || "local Mongo did not become ready";',
1449
- ' throw new Error(`target_local_mongo_unavailable: ${detail}`);',
1337
+ ' throw new Error("target_local_mongo_unavailable: " + detail);',
1450
1338
  '}',
1451
1339
  '',
1452
1340
  'function requireMongo() {',
1453
- ' const candidates = [',
1454
- ' path.join(projectRoot, "server", "node_modules", "mongodb"),',
1455
- ' path.join(projectRoot, "node_modules", "mongodb"),',
1456
- ' path.join(repoRoot, "node_modules", "mongodb"),',
1457
- ' "mongodb"',
1458
- ' ];',
1341
+ ' const candidates = [path.join(projectRoot, "server", "node_modules", "mongodb"), path.join(projectRoot, "node_modules", "mongodb"), path.join(repoRoot, "node_modules", "mongodb"), "mongodb"];',
1459
1342
  ' const errors = [];',
1460
- ' for (const candidate of candidates) {',
1461
- ' try { return require(candidate); } catch (error) { errors.push(`${candidate}: ${error.message}`); }',
1462
- ' }',
1463
- ' throw new Error(`Unable to require mongodb package. ${errors.join(" | ")}`);',
1464
- '}',
1465
- '',
1466
- 'function idValues(docs, keys) {',
1467
- ' const values = new Set();',
1468
- ' for (const doc of docs || []) {',
1469
- ' for (const key of keys) {',
1470
- ' const value = doc && doc[key];',
1471
- ' if (typeof value === "string" && value.trim()) values.add(value.trim());',
1472
- ' if (Array.isArray(value)) value.forEach((entry) => typeof entry === "string" && entry.trim() && values.add(entry.trim()));',
1473
- ' }',
1474
- ' }',
1475
- ' return Array.from(values);',
1343
+ ' for (const candidate of candidates) { try { return require(candidate); } catch (error) { errors.push(candidate + ": " + error.message); } }',
1344
+ ' throw new Error("Unable to require mongodb package. " + errors.join(" | "));',
1476
1345
  '}',
1477
1346
  '',
1478
1347
  'function unique(values) { return Array.from(new Set((values || []).filter(Boolean).map(String))); }',
1479
- '',
1480
- 'function lowerTrim(value) { return String(value || "").trim().toLowerCase(); }',
1481
- '',
1482
- 'function escapeRegexText(value) { return String(value || "").replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&"); }',
1483
- '',
1484
- 'function exactTextRegex(value) { return new RegExp(`^${escapeRegexText(value)}$`, "i"); }',
1485
- '',
1486
- 'function requireOptionalXlsx() {',
1487
- ' const candidates = [',
1488
- ' path.join(projectRoot, "server", "node_modules", "xlsx"),',
1489
- ' path.join(projectRoot, "node_modules", "xlsx"),',
1490
- ' path.join(repoRoot, "node_modules", "xlsx"),',
1491
- ' path.join(process.cwd(), "server", "node_modules", "xlsx"),',
1492
- ' path.join(process.cwd(), "node_modules", "xlsx"),',
1493
- ' "xlsx"',
1494
- ' ];',
1495
- ' for (const candidate of candidates) { try { return require(candidate); } catch (error) {} }',
1496
- ' return null;',
1497
- '}',
1498
- '',
1499
- 'function collectPricingImportWorkbookPaths() {',
1500
- ' const dirs = [',
1501
- ' path.join(projectRoot, ".resolveio-context", "attachments"),',
1502
- ' path.join(repoRoot, ".resolveio-context", "attachments"),',
1503
- ' path.join(projectRoot, ".resolveio-support-context", "attachments"),',
1504
- ' path.join(repoRoot, ".resolveio-support-context", "attachments")',
1505
- ' ];',
1506
- ' const paths = [];',
1507
- ' for (const dir of dirs) {',
1508
- ' try {',
1509
- ' for (const name of fs.readdirSync(dir)) {',
1510
- ' if (/\\.xlsx$/i.test(name) && !/^~\\$/.test(name)) paths.push(path.join(dir, name));',
1511
- ' }',
1512
- ' } catch (error) {}',
1513
- ' }',
1514
- ' return unique(paths);',
1515
- '}',
1516
- '',
1517
- 'function normalizeWorkbookCell(value) {',
1518
- ' if (value == null) return "";',
1519
- ' if (value instanceof Date) return value.toISOString();',
1520
- ' return String(value).replace(/\\s+/g, " ").trim();',
1521
- '}',
1522
- '',
1523
- 'function extractPricingImportWorkbookHints(summary) {',
1524
- ' const workbooks = collectPricingImportWorkbookPaths();',
1525
- ' const xlsx = workbooks.length ? requireOptionalXlsx() : null;',
1526
- ' const customerNames = new Set();',
1527
- ' const itemMap = new Map();',
1528
- ' const workbookSummaries = [];',
1529
- ' if (!workbooks.length) return { workbooks, workbook_summaries: workbookSummaries, customer_names: [], items: [] };',
1530
- ' if (!xlsx) {',
1531
- ' summary.notes.push("Pricing-import workbook seed context skipped because xlsx could not be required in the QA seeder.");',
1532
- ' return { workbooks, workbook_summaries: workbookSummaries, customer_names: [], items: [] };',
1533
- ' }',
1534
- ' for (const workbookPath of workbooks.slice(0, 8)) {',
1535
- ' try {',
1536
- ' const workbook = xlsx.readFile(workbookPath, { cellDates: true });',
1537
- ' const sheetName = workbook.SheetNames.includes("Prices") ? "Prices" : workbook.SheetNames[0];',
1538
- ' const rows = sheetName ? xlsx.utils.sheet_to_json(workbook.Sheets[sheetName], { defval: "" }) : [];',
1539
- ' for (const row of rows) {',
1540
- ' const customer = normalizeWorkbookCell(row.Customer || row.customer || row.CustomerName || row["Customer Name"]);',
1541
- ' const pricingType = normalizeWorkbookCell(row["Pricing Type"] || row.PricingType || row.Type || row.type);',
1542
- ' const name = normalizeWorkbookCell(row.Name || row.name || row.Item || row.item || row.Chemical || row.chemical);',
1543
- ' if (customer) customerNames.add(customer);',
1544
- ' if (name) itemMap.set(`${lowerTrim(pricingType)}::${lowerTrim(name)}`, { name, type: pricingType });',
1545
- ' }',
1546
- ' workbookSummaries.push({ name: path.basename(workbookPath), sheetName, rows: rows.length });',
1547
- ' } catch (error) {',
1548
- ' workbookSummaries.push({ name: path.basename(workbookPath), error: error && (error.message || String(error)) || String(error) });',
1549
- ' }',
1550
- ' }',
1551
- ' return { workbooks, workbook_summaries: workbookSummaries, customer_names: Array.from(customerNames).slice(0, 300), items: Array.from(itemMap.values()).slice(0, 700) };',
1552
- '}',
1553
- '',
1554
- 'async function copyPricingImportWorkbookContext(sourceDb, targetDb, summary) {',
1555
- ' if (!shouldSeedPricingImportFixtures()) return [];',
1556
- ' const hints = extractPricingImportWorkbookHints(summary);',
1557
- ' summary.selected.pricing_import_workbook_seed = {',
1558
- ' workbook_names: hints.workbooks.map((entry) => path.basename(entry)),',
1559
- ' workbook_summaries: hints.workbook_summaries,',
1560
- ' customer_name_count: hints.customer_names.length,',
1561
- ' item_name_count: hints.items.length',
1562
- ' };',
1563
- ' if (!hints.workbooks.length) {',
1564
- ' summary.notes.push("Pricing-import seed profile found no attached .xlsx workbooks to derive customer/item fixture context.");',
1565
- ' return [];',
1566
- ' }',
1567
- ' const customerRegexes = hints.customer_names.map(exactTextRegex);',
1568
- ' const customers = customerRegexes.length ? await copyQuery(sourceDb, targetDb, "customers", { $or: [{ name: { $in: customerRegexes } }, { customer: { $in: customerRegexes } }, { company_name: { $in: customerRegexes } }] }, summary, 300, { name: 1 }) : [];',
1569
- ' const itemOrs = [];',
1570
- ' for (const hint of hints.items) {',
1571
- ' const nameRegex = exactTextRegex(hint.name);',
1572
- ' const typeRegex = hint.type ? exactTextRegex(hint.type) : null;',
1573
- ' if (typeRegex) itemOrs.push({ name: nameRegex, type: typeRegex }, { description: nameRegex, type: typeRegex });',
1574
- ' itemOrs.push({ name: nameRegex }, { description: nameRegex });',
1575
- ' }',
1576
- ' const items = itemOrs.length ? await copyQuery(sourceDb, targetDb, "items", { $or: itemOrs }, summary, 900, { name: 1 }) : [];',
1577
- ' const itemNameRegexes = hints.items.map((hint) => exactTextRegex(hint.name));',
1578
- ' const chemicals = itemNameRegexes.length ? await copyQuery(sourceDb, targetDb, "chemicals", { $or: [{ name: { $in: itemNameRegexes } }, { description: { $in: itemNameRegexes } }] }, summary, 900, { name: 1 }) : [];',
1579
- ' const chemicalIds = unique(chemicals.map((doc) => doc._id));',
1580
- ' const chemicalItems = chemicalIds.length ? await copyQuery(sourceDb, targetDb, "items", { $or: [{ id_chemical: { $in: chemicalIds } }, { _id: { $in: chemicalIds } }] }, summary, 900, { name: 1 }) : [];',
1581
- ' const copiedItems = [...items, ...chemicalItems];',
1582
- ' const copiedCustomerNames = new Set(customers.map((doc) => lowerTrim(doc.name || doc.customer || doc.company_name)));',
1583
- ' const copiedItemNames = new Set(copiedItems.map((doc) => lowerTrim(doc.name || doc.description)));',
1584
- ' const copiedChemicalNames = new Set(chemicals.map((doc) => lowerTrim(doc.name || doc.description)));',
1585
- ' const missingCustomers = hints.customer_names.filter((name) => !copiedCustomerNames.has(lowerTrim(name)));',
1586
- ' const missingItems = hints.items.filter((hint) => !copiedItemNames.has(lowerTrim(hint.name)) && !copiedChemicalNames.has(lowerTrim(hint.name))).map((hint) => hint.type ? `${hint.type}:${hint.name}` : hint.name);',
1587
- ' const itemIds = unique(copiedItems.map((doc) => doc._id));',
1588
- ' const customerIds = unique(customers.map((doc) => doc._id));',
1589
- ' summary.selected.pricing_import_workbook_context = {',
1590
- ' workbook_names: hints.workbooks.map((entry) => path.basename(entry)),',
1591
- ' customer_names: hints.customer_names.slice(0, 60),',
1592
- ' item_names: hints.items.map((hint) => hint.type ? `${hint.type}:${hint.name}` : hint.name).slice(0, 100),',
1593
- ' customer_ids: customerIds,',
1594
- ' item_ids: itemIds,',
1595
- ' chemical_ids: chemicalIds,',
1596
- ' missing_customer_names: missingCustomers.slice(0, 80),',
1597
- ' missing_item_names: missingItems.slice(0, 120)',
1598
- ' };',
1599
- ' summary.notes.push(`Seeded pricing-import workbook context: ${customers.length}/${hints.customer_names.length} customer name(s), ${copiedItems.length}/${hints.items.length} item name(s), ${chemicals.length} chemical(s) from ${hints.workbooks.length} attached workbook(s).`);',
1600
- ' if (missingCustomers.length || missingItems.length) summary.notes.push(`Pricing-import workbook context still missing ${missingCustomers.length} customer name(s) and ${missingItems.length} item name(s) from live Mongo.`);',
1601
- ' return [...customers, ...copiedItems, ...chemicals];',
1602
- '}',
1603
- '',
1604
1348
  'function collectEmailLikeValues(value, out = new Set(), depth = 0) {',
1605
1349
  ' if (depth > 6 || value == null) return out;',
1606
- ' if (typeof value === "string") {',
1607
- ' const matches = value.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/gi) || [];',
1608
- ' matches.forEach((email) => out.add(String(email || "").trim().toLowerCase()));',
1609
- ' return out;',
1610
- ' }',
1350
+ ' if (typeof value === "string") { const matches = value.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/gi) || []; matches.forEach((email) => out.add(String(email || "").trim().toLowerCase())); return out; }',
1611
1351
  ' if (Array.isArray(value)) { value.forEach((entry) => collectEmailLikeValues(entry, out, depth + 1)); return out; }',
1612
1352
  ' if (typeof value === "object") { Object.values(value).forEach((entry) => collectEmailLikeValues(entry, out, depth + 1)); }',
1613
1353
  ' return out;',
1614
1354
  '}',
1615
1355
  '',
1616
1356
  'function readContextJsonFiles() {',
1617
- ' const files = [',
1618
- ' path.join(artifactDir, "manual-ticket.metadata.json"),',
1619
- ' path.join(artifactDir, "email.metadata.json"),',
1620
- ' path.join(repoRoot, ".resolveio-support-context", "manual-ticket.metadata.json"),',
1621
- ' path.join(projectRoot, ".resolveio-support-context", "manual-ticket.metadata.json"),',
1622
- ' path.join(repoRoot, ".resolveio-support-context", "email.metadata.json"),',
1623
- ' path.join(projectRoot, ".resolveio-support-context", "email.metadata.json"),',
1624
- ' path.join(repoRoot, ".resolveio-context", "manual-ticket.metadata.json"),',
1625
- ' path.join(projectRoot, ".resolveio-context", "manual-ticket.metadata.json"),',
1626
- ' path.join(repoRoot, ".resolveio-context", "email.metadata.json"),',
1627
- ' path.join(projectRoot, ".resolveio-context", "email.metadata.json")',
1628
- ' ];',
1629
- ' return files.map((file) => readJsonIfExists(file)).filter(Boolean);',
1357
+ ' const files = [path.join(artifactDir, "manual-ticket.metadata.json"), path.join(artifactDir, "email.metadata.json"), path.join(repoRoot, ".resolveio-support-context", "manual-ticket.metadata.json"), path.join(projectRoot, ".resolveio-support-context", "manual-ticket.metadata.json"), path.join(repoRoot, ".resolveio-support-context", "email.metadata.json"), path.join(projectRoot, ".resolveio-support-context", "email.metadata.json"), path.join(repoRoot, ".resolveio-context", "manual-ticket.metadata.json"), path.join(projectRoot, ".resolveio-context", "manual-ticket.metadata.json"), path.join(repoRoot, ".resolveio-context", "email.metadata.json"), path.join(projectRoot, ".resolveio-context", "email.metadata.json")];',
1358
+ ' return files.map((candidate) => readJsonIfExists(candidate)).filter(Boolean);',
1630
1359
  '}',
1631
- '',
1632
1360
  'function extractQaUserHints() {',
1633
- ' const affected = unique([',
1634
- ' process.env.RESOLVEIO_QA_AFFECTED_USER_EMAIL,',
1635
- ' process.env.RESOLVEIO_SUPPORT_QA_AFFECTED_USER_EMAIL,',
1636
- ' process.env.RESOLVEIO_RUNNER_QA_AFFECTED_USER_EMAIL',
1637
- ' ].map((value) => String(value || "").trim().toLowerCase()).filter(Boolean));',
1638
- ' const reporter = unique([',
1639
- ' process.env.RESOLVEIO_QA_TICKET_REPORTER_EMAIL,',
1640
- ' process.env.RESOLVEIO_SUPPORT_QA_TICKET_REPORTER_EMAIL,',
1641
- ' process.env.RESOLVEIO_RUNNER_QA_TICKET_REPORTER_EMAIL',
1642
- ' ].map((value) => String(value || "").trim().toLowerCase()).filter(Boolean));',
1643
- ' const explicitQa = unique([',
1644
- ' process.env.RESOLVEIO_QA_USERNAME,',
1645
- ' process.env.RESOLVEIO_SUPPORT_QA_USERNAME,',
1646
- ' process.env.RESOLVEIO_RUNNER_QA_USERNAME',
1647
- ' ].map((value) => String(value || "").trim().toLowerCase()).filter((value) => value && !/^(admin|dev@resolveio\\.com)$/i.test(value)));',
1648
- ' const contextEmails = new Set();',
1649
- ' readContextJsonFiles().forEach((json) => collectEmailLikeValues(json, contextEmails));',
1650
- ' collectEmailLikeValues(readSeedHintText(), contextEmails);',
1361
+ ' const affected = unique([process.env.RESOLVEIO_QA_AFFECTED_USER_EMAIL, process.env.RESOLVEIO_SUPPORT_QA_AFFECTED_USER_EMAIL, process.env.RESOLVEIO_RUNNER_QA_AFFECTED_USER_EMAIL].map((value) => String(value || "").trim().toLowerCase()).filter(Boolean));',
1362
+ ' const reporter = unique([process.env.RESOLVEIO_QA_TICKET_REPORTER_EMAIL, process.env.RESOLVEIO_SUPPORT_QA_TICKET_REPORTER_EMAIL, process.env.RESOLVEIO_RUNNER_QA_TICKET_REPORTER_EMAIL].map((value) => String(value || "").trim().toLowerCase()).filter(Boolean));',
1363
+ ' const explicitQa = unique([process.env.RESOLVEIO_QA_USERNAME, process.env.RESOLVEIO_SUPPORT_QA_USERNAME, process.env.RESOLVEIO_RUNNER_QA_USERNAME].map((value) => String(value || "").trim().toLowerCase()).filter((value) => value && !/^(admin|dev@resolveio\\.com)$/i.test(value)));',
1364
+ ' const contextEmails = new Set(); readContextJsonFiles().forEach((json) => collectEmailLikeValues(json, contextEmails)); collectEmailLikeValues(readSeedHintText(), contextEmails);',
1651
1365
  ' const candidates = unique([...affected, ...reporter, ...explicitQa, ...Array.from(contextEmails)]);',
1652
1366
  ' return { affected, reporter, explicitQa, candidates: candidates.slice(0, 12) };',
1653
1367
  '}',
1654
- '',
1655
- 'function qaUserLookupQuery(candidates) {',
1656
- ' const ors = [];',
1657
- ' for (const candidate of candidates || []) {',
1658
- ' const value = String(candidate || "").trim();',
1659
- ' if (!value) continue;',
1660
- ' ors.push({ username: value }, { email: value }, { "emails.address": value });',
1661
- ' }',
1662
- ' return ors.length ? { $or: ors } : null;',
1663
- '}',
1664
- '',
1368
+ 'function qaUserLookupQuery(candidates) { const ors = []; for (const candidate of candidates || []) { const value = String(candidate || "").trim(); if (!value) continue; ors.push({ username: value }, { email: value }, { "emails.address": value }); } return ors.length ? { $or: ors } : null; }',
1665
1369
  'function normalizeSeededQaUser(doc, password) {',
1666
- ' const now = new Date();',
1667
- ' const next = { ...(doc || {}) };',
1668
- ' const other = next.other && typeof next.other === "object" ? next.other : {};',
1669
- ' const settings = next.settings && typeof next.settings === "object" ? next.settings : {};',
1670
- ' next.active = true;',
1671
- ' next.readonly = false;',
1672
- ' next.other = { ...other, tour_completed: true, took_tour: true, core_tour_completed: true, welcome_tour_completed: true, top_navigation_tour_completed: true, user_settings_tour_completed: true };',
1673
- ' next.settings = { ...settings, collapsable_menu: false };',
1674
- ' next.updatedAt = now;',
1675
- ' if (password) {',
1676
- ' const salt = crypto.randomBytes(32).toString("hex");',
1677
- ' next.salt = salt;',
1678
- ' next.hash = crypto.pbkdf2Sync(password, salt, 25000, 512, "sha256").toString("hex");',
1679
- ' if (next.services && typeof next.services === "object" && next.services.password) delete next.services.password;',
1680
- ' next.attempts = 0;',
1681
- ' }',
1370
+ ' const now = new Date(); const next = { ...(doc || {}) }; const other = next.other && typeof next.other === "object" ? next.other : {}; const settings = next.settings && typeof next.settings === "object" ? next.settings : {};',
1371
+ ' next.active = true; next.readonly = false; next.other = { ...other, tour_completed: true, took_tour: true, core_tour_completed: true, welcome_tour_completed: true, top_navigation_tour_completed: true, user_settings_tour_completed: true }; next.settings = { ...settings, collapsable_menu: false }; next.updatedAt = now;',
1372
+ ' if (password) { const salt = crypto.randomBytes(32).toString("hex"); next.salt = salt; next.hash = crypto.pbkdf2Sync(password, salt, 25000, 512, "sha256").toString("hex"); if (next.services && typeof next.services === "object" && next.services.password) delete next.services.password; next.attempts = 0; }',
1682
1373
  ' return next;',
1683
1374
  '}',
1684
- '',
1685
1375
  'async function copyQaIdentityUsers(sourceDb, targetDb, summary) {',
1686
- ' const hints = extractQaUserHints();',
1687
- ' summary.selected.qa_user_hints = hints;',
1688
- ' const query = qaUserLookupQuery(hints.candidates);',
1689
- ' if (!query) return [];',
1690
- ' const users = await sourceDb.collection("users").find(query).limit(8).toArray();',
1691
- ' if (!users.length) {',
1692
- ' summary.notes.push(`No live user matched QA identity hints: ${hints.candidates.join(", ")}`);',
1693
- ' return [];',
1694
- ' }',
1376
+ ' const hints = extractQaUserHints(); summary.selected.qa_user_hints = hints; const query = qaUserLookupQuery(hints.candidates); if (!query) return [];',
1377
+ ' const users = await sourceDb.collection("users").find(query).limit(8).toArray(); if (!users.length) { summary.notes.push("No live user matched QA identity hints: " + hints.candidates.join(", ")); return []; }',
1695
1378
  ' const password = process.env.RESOLVEIO_RUNNER_QA_PASSWORD || process.env.RESOLVEIO_SUPPORT_QA_PASSWORD || "";',
1696
- ' for (const user of users) {',
1697
- ' const normalized = normalizeSeededQaUser(user, password);',
1698
- ' await targetDb.collection("users").replaceOne({ _id: normalized._id }, normalized, { upsert: true });',
1699
- ' }',
1379
+ ' for (const user of users) { const normalized = normalizeSeededQaUser(user, password); await targetDb.collection("users").replaceOne({ _id: normalized._id }, normalized, { upsert: true }); }',
1700
1380
  ' summary.collections.users = (summary.collections.users || 0) + users.length;',
1701
- ' const preferred = users.find((user) => hints.affected.includes(String(user.email || user.username || "").toLowerCase()))',
1702
- ' || users.find((user) => hints.reporter.includes(String(user.email || user.username || "").toLowerCase()))',
1703
- ' || users[0];',
1704
- ' summary.selected.qa_user_context = {',
1705
- ' preferred_username: String(preferred && (preferred.email || preferred.username) || "").trim(),',
1706
- ' preferred_email: String(preferred && preferred.email || "").trim(),',
1707
- ' preferred_user_id: String(preferred && preferred._id || "").trim(),',
1708
- ' affected_user_email: hints.affected[0] || "",',
1709
- ' reporter_email: hints.reporter[0] || "",',
1710
- ' copied_user_ids: users.map((user) => String(user && user._id || "")).filter(Boolean),',
1711
- ' reason: hints.affected.length ? "affected_user" : (hints.reporter.length ? "ticket_reporter" : "context_email")',
1712
- ' };',
1713
- ' summary.notes.push(`Seeded ${users.length} live QA identity user(s); preferred browser QA user is ${summary.selected.qa_user_context.preferred_username || summary.selected.qa_user_context.preferred_user_id}.`);',
1381
+ ' const preferred = users.find((user) => hints.affected.includes(String(user.email || user.username || "").toLowerCase())) || users.find((user) => hints.reporter.includes(String(user.email || user.username || "").toLowerCase())) || users[0];',
1382
+ ' summary.selected.qa_user_context = { preferred_username: String(preferred && (preferred.email || preferred.username) || "").trim(), preferred_email: String(preferred && preferred.email || "").trim(), preferred_user_id: String(preferred && preferred._id || "").trim(), affected_user_email: hints.affected[0] || "", reporter_email: hints.reporter[0] || "", copied_user_ids: users.map((user) => String(user && user._id || "")).filter(Boolean), reason: hints.affected.length ? "affected_user" : (hints.reporter.length ? "ticket_reporter" : "context_email") };',
1383
+ ' summary.notes.push("Seeded " + users.length + " live QA identity user(s); preferred browser QA user is " + (summary.selected.qa_user_context.preferred_username || summary.selected.qa_user_context.preferred_user_id) + ".");',
1714
1384
  ' return users;',
1715
1385
  '}',
1716
- '',
1717
1386
  'function readSeedHintText() {',
1718
- ' const chunks = [',
1719
- ' process.env.RESOLVEIO_SUPPORT_QA_ROW_FILTER || "",',
1720
- ' process.env.RESOLVEIO_SUPPORT_QA_CURRENT_ROW_WORKFLOW || "",',
1721
- ' process.env.RESOLVEIO_SUPPORT_QA_CURRENT_ROW_ASSERTION || "",',
1722
- ' process.env.RESOLVEIO_RUNNER_QA_ROW_FILTER || "",',
1723
- ' process.env.RESOLVEIO_RUNNER_QA_CURRENT_ROW_WORKFLOW || "",',
1724
- ' process.env.RESOLVEIO_RUNNER_QA_CURRENT_ROW_ASSERTION || "",',
1725
- ' process.env.RESOLVEIO_QA_SEED_HINTS || "",',
1726
- ' process.env.RESOLVEIO_SUPPORT_QA_SEED_HINTS || "",',
1727
- ' process.env.RESOLVEIO_RUNNER_QA_SEED_HINTS || ""',
1728
- ' ];',
1729
- ' const candidates = [',
1730
- ' path.join(artifactDir, "qa-coverage-matrix.json"),',
1731
- ' path.join(artifactDir, "qa-row-lock.json"),',
1732
- ' path.join(artifactDir, "runner-evidence", "diagnosis-gate.json"),',
1733
- ' path.join(artifactDir, "runner-evidence", "support-diagnosis-gate.json"),',
1734
- ' path.join(projectRoot, "runner-evidence", "diagnosis-gate.json"),',
1735
- ' path.join(projectRoot, "runner-evidence", "support-diagnosis-gate.json"),',
1736
- ' path.join(repoRoot, ".resolveio-support-context", "manual-ticket.request.txt"),',
1737
- ' path.join(projectRoot, ".resolveio-support-context", "manual-ticket.request.txt"),',
1738
- ' path.join(repoRoot, ".resolveio-context", "manual-ticket.request.txt"),',
1739
- ' path.join(projectRoot, ".resolveio-context", "manual-ticket.request.txt")',
1740
- ' ];',
1741
- ' for (const candidate of candidates) {',
1742
- ' try {',
1743
- ' const raw = fs.readFileSync(candidate, "utf8");',
1744
- ' chunks.push(raw);',
1745
- ' if (/qa-coverage-matrix\\.json$/.test(candidate)) {',
1746
- ' const matrix = JSON.parse(raw);',
1747
- ' const rows = Array.isArray(matrix && matrix.rows) ? matrix.rows : [];',
1748
- ' for (const row of rows) chunks.push([row.workflow, row.route, row.assertion, row.required_proof, row.data_id, row.data_name, row.blocker, row.evidence, row.caption].filter(Boolean).join("\\n"));',
1749
- ' }',
1750
- ' if (/qa-row-lock\\.json$/.test(candidate)) {',
1751
- ' const lock = JSON.parse(raw);',
1752
- ' chunks.push([lock.workflow, lock.route, lock.assertion, lock.required_proof, lock.rowFilter, lock.blocker].filter(Boolean).join("\\n"));',
1753
- ' }',
1754
- ' } catch (error) {}',
1755
- ' }',
1756
- ' return chunks.filter(Boolean).join("\\n");',
1757
- '}',
1758
- '',
1759
- 'function shouldSeedPartInventorySnapshotContext() {',
1760
- ' const text = readSeedHintText();',
1761
- ' const snapshotSurface = /\\b(reportDailyInventorySnapshot|inventory-daily-snapshots|daily\\s+inventory\\s+snapshot|inventory\\s+snapshot|saved\\s+Part\\s+rows|Part\\s+rows|warehouse_location)\\b/i.test(text);',
1762
- ' const partSurface = /\\b(id_part|part\\s+inventory|affected\\s+part|Parts|Kermit|Midland|warehouse|yard)\\b/i.test(text);',
1763
- ' return snapshotSurface && partSurface;',
1764
- '}',
1765
- '',
1766
- 'function extractPartInventorySnapshotHints() {',
1767
- ' const text = readSeedHintText();',
1768
- ' const partIds = new Set();',
1769
- ' const yardIds = new Set();',
1770
- ' const dayKeys = new Set();',
1771
- ' let match;',
1772
- ' const partPattern = /\\b(?:id_part|part\\s*id|affected\\s+part)\\b[^0-9a-f]{0,100}([0-9a-f]{24})\\b/gi;',
1773
- ' while ((match = partPattern.exec(text)) !== null) partIds.add(String(match[1]).toLowerCase());',
1774
- ' const yardPattern = /\\b(?:id_yard|yard\\s*id)\\b[^0-9a-f]{0,100}([0-9a-f]{24})\\b/gi;',
1775
- ' while ((match = yardPattern.exec(text)) !== null) yardIds.add(String(match[1]).toLowerCase());',
1776
- ' const dayKeyPattern = /\\b(?:day_key|snapshot\\s+date|report\\s+date|date)\\b\\s*[=:]?\\s*[`\\\'"]?(\\d{4}-\\d{2}-\\d{2})\\b/gi;',
1777
- ' while ((match = dayKeyPattern.exec(text)) !== null) dayKeys.add(match[1]);',
1778
- ' if (shouldSeedPartInventorySnapshotContext()) {',
1779
- ' const contextualHex = /\\b[0-9a-f]{24}\\b/gi;',
1780
- ' while ((match = contextualHex.exec(text)) !== null && partIds.size < 12) {',
1781
- ' const start = Math.max(0, match.index - 120);',
1782
- ' const end = Math.min(text.length, match.index + 144);',
1783
- ' const around = text.slice(start, end);',
1784
- ' const before = text.slice(Math.max(0, match.index - 48), match.index);',
1785
- ' const after = text.slice(match.index, Math.min(text.length, match.index + 72));',
1786
- ' if (/\\b(id_yard|yard\\s*id)\\b/i.test(before)) { yardIds.add(String(match[0]).toLowerCase()); continue; }',
1787
- ' if (/\\b(id_part|part\\s*id|affected\\s+part)\\b/i.test(before + after)) partIds.add(String(match[0]).toLowerCase());',
1788
- ' }',
1789
- ' }',
1790
- ' return { partIds: Array.from(partIds).slice(0, 16), yardIds: Array.from(yardIds).slice(0, 16), dayKeys: Array.from(dayKeys).slice(0, 12) };',
1791
- '}',
1792
- '',
1793
- 'function isUsefulSeedIdentifier(value) {',
1794
- ' const cleaned = String(value || "").trim().replace(/^[#:\\-]+|[.,;:)\\]]+$/g, "");',
1795
- ' if (!cleaned) return false;',
1796
- ' if (/^00?4\\d{3}$/.test(cleaned)) return false;',
1797
- ' if (/^(screen|tab|are|which|and|render|context|workflow|build|snapshot|state|charge|plan|data|document|delivery|deliveries)$/i.test(cleaned)) return false;',
1798
- ' return /\\d/.test(cleaned) || /^[A-Z][A-Z0-9._-]{3,}$/.test(cleaned);',
1799
- '}',
1800
- '',
1801
- 'function extractBolIdentifiers() {',
1802
- ' const text = readSeedHintText();',
1803
- ' const identifiers = new Set();',
1804
- ' const explicit = /\\bBOL\\s*(?:#|no\\.?|number|num)?\\s*[:#-]?\\s*([A-Z0-9][A-Z0-9._-]{2,})\\b/gi;',
1805
- ' let match;',
1806
- ' while ((match = explicit.exec(text)) !== null) {',
1807
- ' const value = String(match[1] || "").trim();',
1808
- ' if (isUsefulSeedIdentifier(value)) identifiers.add(value);',
1809
- ' }',
1810
- ' return Array.from(identifiers).slice(0, 8);',
1811
- '}',
1812
- '',
1813
- 'function shouldAutoDiscoverTruckTreatingBolContext() {',
1814
- ' const text = readSeedHintText();',
1815
- ' return /\\b(bol|bill\\s+of\\s+lading|truck\\s*treat(?:ing)?|truck-treating|deliver(?:y|ed|ies)?\\s+BOL)\\b/i.test(text);',
1816
- '}',
1817
- '',
1818
- 'function shouldAutoDiscoverAssetContext() {',
1819
- ' const text = readSeedHintText();',
1820
- ' return /\\b(asset|assets|unit|units|equipment|current\\s+location|location\\s+name|location\\s+names|default\\s+yard|yard)\\b/i.test(text);',
1821
- '}',
1822
- '',
1823
- 'function shouldAutoDiscoverProductionInterchangeablesContext() {',
1824
- ' const text = readSeedHintText();',
1825
- ' return /\\b(interchangeable|interchangeables|mass\\s+update|update\\s+interchangeables|from\\s+chemical|to\\s+chemical|treatment\\s+plan|treatment\\s+plans|production\\s+location)\\b/i.test(text);',
1826
- '}',
1827
- '',
1828
- 'function extractAssetIdentifiers() {',
1829
- ' const text = readSeedHintText();',
1830
- ' const identifiers = new Set();',
1831
- ' const explicit = /\\b(?:asset|unit|equipment)\\s*(?:#|no\\.?|number|num)?\\s*[:#-]?\\s*([A-Z0-9][A-Z0-9._-]{1,})\\b/gi;',
1832
- ' let match;',
1833
- ' while ((match = explicit.exec(text)) !== null) {',
1834
- ' const value = String(match[1] || "").trim();',
1835
- ' if (isUsefulSeedIdentifier(value)) identifiers.add(value);',
1836
- ' }',
1837
- ' const typedUnit = /\\b(?:truck|trailer|unit|asset)\\s+([A-Z0-9][A-Z0-9._-]{1,})\\b/gi;',
1838
- ' while ((match = typedUnit.exec(text)) !== null) {',
1839
- ' const value = String(match[1] || "").trim();',
1840
- ' if (isUsefulSeedIdentifier(value)) identifiers.add(value);',
1841
- ' }',
1842
- ' if (shouldAutoDiscoverAssetContext()) extractSeedIdentifiers().forEach((value) => identifiers.add(value));',
1843
- ' return Array.from(identifiers).slice(0, 20);',
1844
- '}',
1845
- '',
1846
- 'function extractRouteNameHints() {',
1847
- ' const text = readSeedHintText();',
1848
- ' const routes = new Set();',
1849
- ' const routePattern = /\\b(?:on|for)\\s+(?:the\\s+)?([A-Za-z0-9][A-Za-z0-9 &._/-]{1,48}?)\\s+route\\b/gi;',
1850
- ' let match;',
1851
- ' while ((match = routePattern.exec(text)) !== null) {',
1852
- ' const routeName = String(match[1] || "").trim().replace(/^(?:the|a|an)\\s+/i, "").replace(/\\s+/g, " ");',
1853
- ' if (routeName && !/^(?:this|that|selected)$/i.test(routeName)) routes.add(routeName);',
1854
- ' }',
1855
- ' return Array.from(routes).slice(0, 8);',
1856
- '}',
1857
- '',
1858
- 'function extractSeedIdentifiers() {',
1859
- ' const text = readSeedHintText();',
1860
- ' const identifiers = new Set();',
1861
- ' const explicit = /\\b(?:invoice|inv|bol|order|pso|ticket|wo|work\\s*order|delivery|treatment)\\s*(?:#|no\\.?|number|num)?\\s*[:#-]?\\s*([A-Z0-9][A-Z0-9._-]{2,})\\b/gi;',
1862
- ' let match;',
1863
- ' while ((match = explicit.exec(text)) !== null) identifiers.add(match[1]);',
1864
- ' const typedUnit = /\\b(?:truck|trailer|asset|unit)\\s+([A-Z0-9][A-Z0-9._-]{1,})\\b/gi;',
1865
- ' while ((match = typedUnit.exec(text)) !== null) identifiers.add(match[1]);',
1866
- ' const numeric = /\\b\\d{3,}\\b/g;',
1867
- ' while ((match = numeric.exec(text)) !== null) identifiers.add(match[0]);',
1868
- ' return Array.from(identifiers)',
1869
- ' .map((value) => String(value || "").trim())',
1870
- ' .filter((value) => isUsefulSeedIdentifier(value))',
1871
- ' .slice(0, 20);',
1872
- '}',
1873
- '',
1874
- 'function desiredSeedProfile() {',
1875
- ' if (shouldSeedPricingImportFixtures()) return "pricing_import";',
1876
- ' if (shouldSeedBillingDashboardFixtures()) return "billing_inventory";',
1877
- ' return "live_context";',
1878
- '}',
1879
- '',
1880
- 'function shouldSeedBillingDashboardFixtures() {',
1881
- ' const force = process.env.RESOLVEIO_QA_FORCE_BILLING_FIXTURES || process.env.RESOLVEIO_SUPPORT_QA_FORCE_BILLING_FIXTURES || process.env.RESOLVEIO_RUNNER_QA_FORCE_BILLING_FIXTURES || "";',
1882
- ' if (/^(true|1|yes|on)$/i.test(force)) return true;',
1883
- ' if (shouldSeedPartInventorySnapshotContext()) return false;',
1884
- ' if (shouldSeedPricingImportFixtures()) return false;',
1885
- ' const explicit = process.env.RESOLVEIO_QA_INCLUDE_BILLING_FIXTURES || process.env.RESOLVEIO_SUPPORT_QA_INCLUDE_BILLING_FIXTURES || process.env.RESOLVEIO_RUNNER_QA_INCLUDE_BILLING_FIXTURES || "";',
1886
- ' if (/^(true|1|yes|on)$/i.test(explicit)) return true;',
1887
- ' if (/^(false|0|no|off)$/i.test(explicit)) return false;',
1888
- ' const text = readSeedHintText();',
1889
- ' const billingSurface = /\\b(invoice|invoicing|billing|billable|tax|taxes|surcharge|checkout|check\\s*out|pick\\s*ticket)\\b/i.test(text);',
1890
- ' if (billingSurface) return true;',
1891
- ' return /\\binventory\\b/i.test(text);',
1892
- '}',
1893
- '',
1894
- 'function shouldSeedPricingImportFixtures() {',
1895
- ' const explicit = process.env.RESOLVEIO_QA_INCLUDE_PRICING_IMPORT_FIXTURES || process.env.RESOLVEIO_SUPPORT_QA_INCLUDE_PRICING_IMPORT_FIXTURES || process.env.RESOLVEIO_RUNNER_QA_INCLUDE_PRICING_IMPORT_FIXTURES || "";',
1896
- ' if (/^(true|1|yes|on)$/i.test(explicit)) return true;',
1897
- ' if (/^(false|0|no|off)$/i.test(explicit)) return false;',
1898
- ' const text = readSeedHintText();',
1899
- ' const pricingSurface = /\\b(pricing|price|gross\\s+price|production\\s+price|manage\\s+pricing)\\b/i.test(text);',
1900
- ' const importSurface = /\\b(import|upload|template|spreadsheet|xlsx|excel|attached\\s+template)\\b/i.test(text);',
1901
- ' return pricingSurface && importSurface;',
1902
- '}',
1903
- '',
1904
- 'function identifierQuery(identifiers) {',
1905
- ' const ors = [];',
1906
- ' const fields = ["_id", "invoice_number", "invoice_number_string", "order_number", "order_number_string", "activity_number", "bol_number", "bol_string", "ticket_number", "ticket", "wo_number", "work_order_number", "delivery_number", "treatment_number"];',
1907
- ' for (const identifier of identifiers || []) {',
1908
- ' const escaped = String(identifier).replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&");',
1909
- ' const regex = new RegExp(escaped, "i");',
1910
- ' for (const field of fields) ors.push({ [field]: regex });',
1911
- ' ors.push({ "bol.bol_string": regex });',
1912
- ' ors.push({ "bol.bol_number": regex });',
1913
- ' }',
1914
- ' return ors.length ? { $or: ors } : null;',
1915
- '}',
1916
- '',
1917
- 'function assetLookupQuery(identifiers) {',
1918
- ' const ors = [];',
1919
- ' const fields = ["_id", "asset_number", "asset_number_string", "asset_unit_number", "unit_number", "number", "serial_number", "name"];',
1920
- ' for (const identifier of identifiers || []) {',
1921
- ' const value = String(identifier || "").trim();',
1922
- ' if (!value) continue;',
1923
- ' const escaped = value.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&");',
1924
- ' const exactText = new RegExp(`^${escaped}$`, "i");',
1925
- ' const containsText = new RegExp(escaped, "i");',
1926
- ' for (const field of fields) ors.push({ [field]: exactText });',
1927
- ' for (const field of ["asset_number_string", "asset_unit_number", "unit_number", "number", "serial_number", "name"]) ors.push({ [field]: containsText });',
1928
- ' if (/^\\d+$/.test(value)) {',
1929
- ' const numericValue = Number(value);',
1930
- ' for (const field of ["asset_number", "unit_number", "number"]) ors.push({ [field]: numericValue });',
1931
- ' }',
1932
- ' }',
1933
- ' return ors.length ? { $or: ors } : null;',
1934
- '}',
1935
- '',
1936
- 'async function copyByIds(sourceDb, targetDb, collectionName, ids, summary, limit = 100) {',
1937
- ' const cleanIds = unique(ids).slice(0, limit);',
1938
- ' if (!cleanIds.length) return [];',
1939
- ' const docs = await sourceDb.collection(collectionName).find({ _id: { $in: cleanIds } }).limit(limit).toArray();',
1940
- ' for (const doc of docs) await targetDb.collection(collectionName).replaceOne({ _id: doc._id }, doc, { upsert: true });',
1941
- ' summary.collections[collectionName] = (summary.collections[collectionName] || 0) + docs.length;',
1942
- ' return docs;',
1943
- '}',
1944
- '',
1945
- 'async function copyQuery(sourceDb, targetDb, collectionName, query, summary, limit = 50, sort = null) {',
1946
- ' let cursor = sourceDb.collection(collectionName).find(query || {});',
1947
- ' if (sort) cursor = cursor.sort(sort);',
1948
- ' const docs = await cursor.limit(limit).toArray();',
1949
- ' for (const doc of docs) await targetDb.collection(collectionName).replaceOne({ _id: doc._id }, doc, { upsert: true });',
1950
- ' summary.collections[collectionName] = (summary.collections[collectionName] || 0) + docs.length;',
1951
- ' return docs;',
1952
- '}',
1953
- '',
1954
- 'function bolLookupQuery(identifiers) {',
1955
- ' const ors = [];',
1956
- ' for (const identifier of identifiers || []) {',
1957
- ' const value = String(identifier || "").trim();',
1958
- ' if (!value) continue;',
1959
- ' const escaped = value.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&");',
1960
- ' const contains = new RegExp(`(^|[^0-9A-Za-z])${escaped}([^0-9A-Za-z]|$)`, "i");',
1961
- ' ors.push({ _id: value }, { bol_number: value }, { bol_string: value }, { bol_number: contains }, { bol_string: contains });',
1962
- ' if (/^\\d+$/.test(value)) ors.push({ bol_number: Number(value) });',
1963
- ' }',
1964
- ' return ors.length ? { $or: ors } : null;',
1387
+ ' const chunks = [process.env.RESOLVEIO_SUPPORT_QA_ROW_FILTER || "", process.env.RESOLVEIO_SUPPORT_QA_CURRENT_ROW_WORKFLOW || "", process.env.RESOLVEIO_SUPPORT_QA_CURRENT_ROW_ASSERTION || "", process.env.RESOLVEIO_RUNNER_QA_ROW_FILTER || "", process.env.RESOLVEIO_RUNNER_QA_CURRENT_ROW_WORKFLOW || "", process.env.RESOLVEIO_RUNNER_QA_CURRENT_ROW_ASSERTION || "", process.env.RESOLVEIO_QA_SEED_HINTS || "", process.env.RESOLVEIO_SUPPORT_QA_SEED_HINTS || "", process.env.RESOLVEIO_RUNNER_QA_SEED_HINTS || ""];',
1388
+ ' const candidates = [path.join(artifactDir, "qa-coverage-matrix.json"), path.join(artifactDir, "qa-row-lock.json"), path.join(artifactDir, "runner-evidence", "diagnosis-gate.json"), path.join(artifactDir, "runner-evidence", "support-diagnosis-gate.json"), path.join(projectRoot, "runner-evidence", "diagnosis-gate.json"), path.join(projectRoot, "runner-evidence", "support-diagnosis-gate.json"), path.join(repoRoot, ".resolveio-support-context", "manual-ticket.request.txt"), path.join(projectRoot, ".resolveio-support-context", "manual-ticket.request.txt"), path.join(repoRoot, ".resolveio-context", "manual-ticket.request.txt"), path.join(projectRoot, ".resolveio-context", "manual-ticket.request.txt")];',
1389
+ ' for (const candidate of candidates) { try { chunks.push(fs.readFileSync(candidate, "utf8")); } catch (error) {} } return chunks.filter(Boolean).join("\\n");',
1390
+ '}',
1391
+ 'function isUsefulSeedIdentifier(value) { const cleaned = String(value || "").trim().replace(/^[#:\\-]+|[.,;:)\\]]+$/g, ""); if (!cleaned) return false; if (/^00?4\\d{3}$/.test(cleaned)) return false; if (/^(screen|tab|are|which|and|render|context|workflow|build|snapshot|state|plan|data|document|record|records)$/i.test(cleaned)) return false; return /\\d/.test(cleaned) || /^[A-Z][A-Z0-9._-]{3,}$/.test(cleaned); }',
1392
+ 'function extractSeedIdentifiers() { const text = readSeedHintText(); const identifiers = new Set(); const explicit = /\\b(?:id|record|number|no\\.?|ref(?:erence)?|case|ticket|job|request|code|key|identifier)\\s*(?:#|no\\.?|number|num|id|ref)?\\s*[:#-]?\\s*([A-Z0-9][A-Z0-9._-]{2,})\\b/gi; let match; while ((match = explicit.exec(text)) !== null) identifiers.add(match[1]); const numeric = /\\b\\d{3,}\\b/g; while ((match = numeric.exec(text)) !== null) identifiers.add(match[0]); const quoted = /["`]([A-Z0-9][A-Z0-9._-]{2,})["`]/g; while ((match = quoted.exec(text)) !== null) identifiers.add(match[1]); return Array.from(identifiers).map((value) => String(value || "").trim()).filter((value) => isUsefulSeedIdentifier(value)).slice(0, 20); }',
1393
+ 'function identifierQuery(identifiers) { const ors = []; const fields = ["_id", "id", "record_id", "recordId", "external_id", "externalId", "reference", "reference_number", "referenceNumber", "case_number", "caseNumber", "ticket_number", "ticketNumber", "request_number", "requestNumber", "number", "number_string", "numberString", "code", "key", "name", "title", "label", "username", "email", "description", "metadata.reference", "metadata.number", "metadata.code"]; for (const identifier of identifiers || []) { const escaped = String(identifier).replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&"); const regex = new RegExp(escaped, "i"); for (const field of fields) ors.push({ [field]: regex }); } return ors.length ? { $or: ors } : null; }',
1394
+ 'async function copyByIds(sourceDb, targetDb, collectionName, ids, summary, limit = 100) { const cleanIds = unique(ids).slice(0, limit); if (!cleanIds.length) return []; const docs = await sourceDb.collection(collectionName).find({ _id: { $in: cleanIds } }).limit(limit).toArray(); for (const doc of docs) await targetDb.collection(collectionName).replaceOne({ _id: doc._id }, doc, { upsert: true }); summary.collections[collectionName] = (summary.collections[collectionName] || 0) + docs.length; return docs; }',
1395
+ 'async function copyQuery(sourceDb, targetDb, collectionName, query, summary, limit = 50, sort = null) { if (!query) return []; let cursor = sourceDb.collection(collectionName).find(query || {}); if (sort) cursor = cursor.sort(sort); const docs = await cursor.limit(limit).toArray(); for (const doc of docs) await targetDb.collection(collectionName).replaceOne({ _id: doc._id }, doc, { upsert: true }); summary.collections[collectionName] = (summary.collections[collectionName] || 0) + docs.length; return docs; }',
1396
+ 'function seedTermsFromHints() { const text = readSeedHintText().toLowerCase(); const stop = new Set("the and for with from into that this these those then than when where what which who why how are was were can cannot should must expected observed actual before after route page screen button field form data value values record records issue bug fix fixed customer user users account workflow proof plan state status result results".split(" ")); const terms = new Set(); for (const match of text.matchAll(/[a-z][a-z0-9_-]{2,}/g)) { const term = String(match[0] || "").replace(/[_-]+/g, "-"); if (stop.has(term)) continue; terms.add(term); const singular = term.endsWith("s") ? term.slice(0, -1) : ""; if (singular && !stop.has(singular) && singular.length > 2) terms.add(singular); } return Array.from(terms).slice(0, 80); }',
1397
+ 'function collectionScore(name, terms, identifiers) { const normalized = String(name || "").toLowerCase().replace(/[^a-z0-9]+/g, " "); if (!normalized || /^(system\\.|oplog|migrations?$|changelog$|locks?$)/i.test(String(name || ""))) return -1; let score = identifiers && identifiers.length ? 1 : 0; for (const term of terms || []) { const clean = String(term || "").replace(/[^a-z0-9]+/g, " ").trim(); if (!clean || clean.length < 3) continue; if (normalized === clean) score += 8; else if (normalized.split(/\\s+/).includes(clean)) score += 5; else if (normalized.includes(clean)) score += 3; } if (/\\busers?\\b/i.test(normalized)) score += 2; return score; }',
1398
+ 'async function discoverSeedCollections(sourceDb, terms, identifiers, summary) { const all = await sourceDb.listCollections({}, { nameOnly: true }).toArray(); const scored = all.map((entry) => ({ name: String(entry && entry.name || ""), score: collectionScore(entry && entry.name, terms, identifiers) })).filter((entry) => entry.name && entry.score >= 0).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name)); const selected = scored.filter((entry) => entry.score > 0).slice(0, 24); const fallback = selected.length ? selected : scored.slice(0, identifiers && identifiers.length ? 24 : 8); summary.selected.seed_collection_terms = (terms || []).slice(0, 40); summary.selected.seed_collection_candidates = fallback.map((entry) => ({ name: entry.name, score: entry.score })).slice(0, 24); return fallback.map((entry) => entry.name); }',
1399
+ 'function collectRelatedIds(docs, limit = 500) { const ids = new Set(); const visit = (value, key, depth) => { if (ids.size >= limit || depth > 5 || value == null) return; const keyText = String(key || ""); const isIdKey = /(^_id$|^id$|^id_|_id$|_ids$|Id$|Ids$)/.test(keyText); if (typeof value === "string") { const clean = value.trim(); if (isIdKey && isUsefulSeedIdentifier(clean)) ids.add(clean); return; } if (Array.isArray(value)) { value.forEach((entry) => visit(entry, keyText, depth + 1)); return; } if (typeof value === "object") { if (typeof value.toHexString === "function" && isIdKey) ids.add(String(value.toHexString())); Object.entries(value).forEach(([childKey, childValue]) => visit(childValue, childKey, depth + 1)); } }; for (const doc of docs || []) visit(doc, "", 0); return Array.from(ids).slice(0, limit); }',
1400
+ 'async function seedSupportLiveContext(sourceDb, targetDb) {',
1401
+ ' const summary = { profile: desiredSeedProfile(), collections: {}, selected: {}, notes: [] }; const identifiers = extractSeedIdentifiers(); const terms = seedTermsFromHints(); summary.selected.seed_identifiers = identifiers; const qaIdentityUsers = await copyQaIdentityUsers(sourceDb, targetDb, summary); const sourceDocs = [...qaIdentityUsers]; const query = identifierQuery(identifiers); const collections = await discoverSeedCollections(sourceDb, terms, identifiers, summary);',
1402
+ ' if (query) { for (const collectionName of collections) { const docs = await copyQuery(sourceDb, targetDb, collectionName, query, summary, 80, { updatedAt: -1 }).catch((error) => { summary.notes.push("Skipped live context collection " + collectionName + ": " + (error && (error.message || String(error)) || String(error))); return []; }); sourceDocs.push(...docs); } }',
1403
+ ' const relatedIds = collectRelatedIds(sourceDocs); summary.selected.related_id_count = relatedIds.length; if (relatedIds.length) { for (const collectionName of collections) { const docs = await copyByIds(sourceDb, targetDb, collectionName, relatedIds, summary, 80).catch((error) => { summary.notes.push("Skipped related-id copy for " + collectionName + ": " + (error && (error.message || String(error)) || String(error))); return []; }); sourceDocs.push(...docs); } }',
1404
+ ' const copiedCount = Object.values(summary.collections).reduce((sum, count) => sum + Number(count || 0), 0); summary.ready = copiedCount > 0; summary.seed_data_unavailable = !summary.ready; if (!query) summary.notes.push("No concrete ticket identifiers were available for live-context seeding; QA should let the proof plan choose browser, method-level, fixture, or explanation-backed evidence."); if (!summary.ready) summary.notes.push("Live-context seed copied no matching documents. Record seed_data_unavailable and choose a fallback proof strategy unless the current proof plan explicitly requires that exact production record."); else summary.notes.push("Seeded live context from ticket/proof identifiers and discovered collections: " + copiedCount + " document(s) across " + Object.keys(summary.collections).length + " collection(s)."); return summary;',
1965
1405
  '}',
1966
- '',
1967
- 'async function discoverTruckTreatingBolIds(sourceDb, routeHints, limit = 5) {',
1968
- ' const routeRegexes = (routeHints || []).map((hint) => new RegExp(String(hint).replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&"), "i"));',
1969
- ' const routeHintMatch = routeRegexes.length ? { $or: [',
1970
- ' { location: { $in: routeRegexes } },',
1971
- ' { well_group: { $in: routeRegexes } },',
1972
- ' { route: { $in: routeRegexes } },',
1973
- ' { route_name: { $in: routeRegexes } }',
1974
- ' ] } : null;',
1975
- ' const baseMatch = {',
1976
- ' id_bol: { $type: "string", $ne: "" },',
1977
- ' items: { $elemMatch: { item_type: { $in: ["Service", "Flush"] } } }',
1978
- ' };',
1979
- ' const pipeline = [',
1980
- ' { $match: routeHintMatch ? { $and: [baseMatch, routeHintMatch] } : baseMatch },',
1981
- ' { $sort: { date: -1, date_ship: -1, updatedAt: -1 } },',
1982
- ' { $group: { _id: "$id_bol", route_count: { $sum: 1 }, latest: { $max: "$updatedAt" } } },',
1983
- ' { $sort: { latest: -1, route_count: -1 } },',
1984
- ' { $limit: limit },',
1985
- ' { $project: { _id: 1 } }',
1986
- ' ];',
1987
- ' const rows = await sourceDb.collection("production-routes").aggregate(pipeline).toArray();',
1988
- ' return rows.map((row) => row && row._id).filter(Boolean);',
1989
- '}',
1990
- '',
1991
- 'function collectNestedStringValues(docs, keys) {',
1992
- ' const values = new Set();',
1993
- ' const visit = (value) => {',
1994
- ' if (!value) return;',
1995
- ' if (Array.isArray(value)) { value.forEach(visit); return; }',
1996
- ' if (typeof value === "object") {',
1997
- ' for (const key of keys) {',
1998
- ' const entry = value[key];',
1999
- ' if (typeof entry === "string" && entry.trim()) values.add(entry.trim());',
2000
- ' else if (Array.isArray(entry) || (entry && typeof entry === "object")) visit(entry);',
2001
- ' }',
2002
- ' for (const nested of Object.values(value)) if (Array.isArray(nested)) visit(nested);',
2003
- ' }',
2004
- ' };',
2005
- ' visit(docs || []);',
2006
- ' return Array.from(values);',
2007
- '}',
2008
- '',
2009
- 'async function copyTruckTreatingBolContext(sourceDb, targetDb, summary) {',
2010
- ' const bolIdentifiers = extractBolIdentifiers();',
2011
- ' const routeHints = extractRouteNameHints();',
2012
- ' summary.selected.bol_identifiers = bolIdentifiers;',
2013
- ' if (routeHints.length) summary.selected.route_name_hints = routeHints;',
2014
- ' let query = bolLookupQuery(bolIdentifiers);',
2015
- ' let discoveryMode = "";',
2016
- ' if (!query && shouldAutoDiscoverTruckTreatingBolContext()) {',
2017
- ' const discoveredBolIds = await discoverTruckTreatingBolIds(sourceDb, routeHints, 8);',
2018
- ' if (discoveredBolIds.length) {',
2019
- ' summary.selected.discovered_truck_treating_bol_ids = discoveredBolIds;',
2020
- ' query = { _id: { $in: discoveredBolIds } };',
2021
- ' discoveryMode = "auto_discovered_from_live_production_routes";',
2022
- ' }',
2023
- ' }',
2024
- ' if (!query) return [];',
2025
- ' let bols = await copyQuery(sourceDb, targetDb, "bols", query, summary, 8, { date_shipped: -1, date: -1, updatedAt: -1 });',
2026
- ' if (!bols.length) {',
2027
- ' summary.notes.push(`No live BOL matched ticket BOL identifiers: ${bolIdentifiers.join(", ")}`);',
2028
- ' if (shouldAutoDiscoverTruckTreatingBolContext()) {',
2029
- ' const discoveredBolIds = await discoverTruckTreatingBolIds(sourceDb, routeHints, 8);',
2030
- ' if (discoveredBolIds.length) {',
2031
- ' summary.selected.discovered_truck_treating_bol_ids = discoveredBolIds;',
2032
- ' discoveryMode = "auto_discovered_after_identifier_miss";',
2033
- ' bols = await copyQuery(sourceDb, targetDb, "bols", { _id: { $in: discoveredBolIds } }, summary, 8, { date_shipped: -1, date: -1, updatedAt: -1 });',
2034
- ' }',
2035
- ' }',
2036
- ' if (!bols.length) return [];',
2037
- ' }',
2038
- ' const bolIds = bols.map((doc) => doc._id).filter(Boolean);',
2039
- ' const productionRoutes = await copyQuery(sourceDb, targetDb, "production-routes", { id_bol: { $in: bolIds } }, summary, 600, { route_order: 1, route_number: 1 });',
2040
- ' const routeIds = productionRoutes.map((doc) => doc._id).filter(Boolean);',
2041
- ' const truckTreatingRouteIds = idValues(productionRoutes, ["id_truck_treating_route"]);',
2042
- ' const productionSalesOrderIds = idValues(productionRoutes, ["id_pso", "id_activity", "id_production_sales_order", "id_sales_order"]);',
2043
- ' const productionDeliveries = await copyQuery(sourceDb, targetDb, "production-deliveries", { $or: [{ id_bol: { $in: bolIds } }, { id_activity: { $in: productionSalesOrderIds } }] }, summary, 600, { route_order: 1, updatedAt: -1 });',
2044
- ' const routeTruckTreatDeliveryIds = collectNestedStringValues(productionRoutes, ["id_truck_treat_delivery", "id_truck_treating_delivery"]);',
2045
- ' const truckTreatingDeliveries = await copyQuery(sourceDb, targetDb, "truck-treating-deliveries", { $or: [{ id_bol: { $in: bolIds } }, { _id: { $in: routeTruckTreatDeliveryIds } }] }, summary, 600, { route_order: 1, updatedAt: -1 });',
2046
- ' const copiedTruckTreatingRoutes = await copyByIds(sourceDb, targetDb, "truck-treating-routes", truckTreatingRouteIds, summary, 200);',
2047
- ' await copyByIds(sourceDb, targetDb, "production-sales-orders", productionSalesOrderIds, summary, 200);',
2048
- ' const locationIds = unique([...idValues(productionRoutes, ["id_location"]), ...idValues(productionDeliveries, ["id_location"]), ...idValues(truckTreatingDeliveries, ["id_location"])]);',
2049
- ' const wellGroupIds = unique([...idValues(productionRoutes, ["id_well_group"]), ...idValues(productionDeliveries, ["id_well_group"]), ...idValues(truckTreatingDeliveries, ["id_well_group"])]);',
2050
- ' const copiedLocations = await copyByIds(sourceDb, targetDb, "production-locations", locationIds, summary, 300);',
2051
- ' const copiedWellGroups = await copyByIds(sourceDb, targetDb, "well-groups", wellGroupIds, summary, 300);',
2052
- ' const copiedTreatmentPlans = await copyQuery(sourceDb, targetDb, "treatment-plans", { $or: [',
2053
- ' { "truck_treating.id_truck_treating_route": { $in: truckTreatingRouteIds } },',
2054
- ' { id_location: { $in: locationIds } },',
2055
- ' { id_well_group: { $in: wellGroupIds } }',
2056
- ' ] }, summary, 800, { route_order: 1, updatedAt: -1 });',
2057
- ' const itemIds = unique([',
2058
- ' ...collectNestedStringValues(productionRoutes, ["id_item", "id_chemical"]),',
2059
- ' ...idValues(productionDeliveries, ["id_item", "id_chemical"]),',
2060
- ' ...idValues(truckTreatingDeliveries, ["id_item", "id_chemical"]),',
2061
- ' ...idValues(copiedTreatmentPlans, ["id_item", "id_chemical"])',
2062
- ' ]);',
2063
- ' await copyByIds(sourceDb, targetDb, "items", itemIds, summary, 300);',
2064
- ' await copyByIds(sourceDb, targetDb, "chemicals", itemIds, summary, 300);',
2065
- ' await copyByIds(sourceDb, targetDb, "customers", unique([...idValues(copiedLocations, ["id_customer"]), ...idValues(copiedWellGroups, ["id_customer"]), ...idValues(productionDeliveries, ["id_customer"]), ...idValues(truckTreatingDeliveries, ["id_customer"])]), summary, 100);',
2066
- ' await copyByIds(sourceDb, targetDb, "users", unique([...idValues(bols, ["id_user", "id_driver"]), ...idValues(productionDeliveries, ["id_user_approved", "id_driver", "id_driver_scheduled", "id_account_manager", "id_foreman"])]), summary, 100);',
2067
- ' await copyQuery(sourceDb, targetDb, "chemical-field-transfers", { id_bol: { $in: bolIds } }, summary, 200, { updatedAt: -1 });',
2068
- ' await copyQuery(sourceDb, targetDb, "generic-samples", { id_bol: { $in: bolIds } }, summary, 200, { updatedAt: -1 });',
2069
- ' const linkedRouteNames = copiedTruckTreatingRoutes.map((doc) => String(doc.name || doc.route || doc.truck_treating_route || doc._id || "")).filter(Boolean);',
2070
- ' const routeHintMatched = routeHints.length ? routeHints.some((hint) => linkedRouteNames.some((name) => name.toLowerCase().includes(hint.toLowerCase()))) : null;',
2071
- ' const driverIds = unique([...idValues(bols, ["id_driver", "id_user"]), ...idValues(productionDeliveries, ["id_driver", "id_driver_scheduled", "id_user_approved"]), ...idValues(truckTreatingDeliveries, ["id_driver", "id_driver_scheduled", "id_user_approved"])]);',
2072
- ' const bolStatuses = bols.map((doc) => ({',
2073
- ' id: String(doc && doc._id || ""),',
2074
- ' bol_number: String(doc && (doc.bol_string || doc.bol_number || "") || ""),',
2075
- ' status: String(doc && doc.status || ""),',
2076
- ' delivered: /delivered/i.test(String(doc && doc.status || ""))',
2077
- ' })).filter((entry) => entry.id);',
2078
- ' const primaryDriverId = driverIds[0] || "";',
2079
- ' const primaryBolId = bolIds[0] || "";',
2080
- ' const primaryBolStatus = String((bolStatuses.find((entry) => entry.id === primaryBolId) || bolStatuses[0] || {}).status || "");',
2081
- ' const primaryBolDelivered = /delivered/i.test(primaryBolStatus);',
2082
- ' const browserRoutes = primaryDriverId && primaryBolId ? {',
2083
- ' delivery: `/dashboard/driver/truck-treating/bol-delivery/${primaryDriverId}/${primaryBolId}`,',
2084
- ' detail: `/dashboard/driver/truck-treating/bol-detail/${primaryDriverId}/${primaryBolId}`,',
2085
- ' list: `/dashboard/driver/truck-treating/bol-list/${primaryDriverId}`',
2086
- ' } : {};',
2087
- ' summary.selected.truck_treating_bol_context = {',
2088
- ' bol_ids: bolIds,',
2089
- ' driver_ids: driverIds,',
2090
- ' route_ids: routeIds.slice(0, 100),',
2091
- ' route_count: productionRoutes.length,',
2092
- ' truck_treating_route_ids: truckTreatingRouteIds,',
2093
- ' linked_truck_treating_routes: linkedRouteNames,',
2094
- ' browser_routes: browserRoutes,',
2095
- ' bol_statuses: bolStatuses,',
2096
- ' primary_bol_status: primaryBolStatus,',
2097
- ' primary_bol_delivered: primaryBolDelivered,',
2098
- ' delivery_route_is_action_route: true,',
2099
- ' delivery_route_requires_not_delivered_bol: true,',
2100
- ' route_name_hints: routeHints,',
2101
- ' route_hint_matched: routeHintMatched',
2102
- ' };',
2103
- ' if (discoveryMode) summary.selected.truck_treating_bol_context.discovery_mode = discoveryMode;',
2104
- ' if (browserRoutes.delivery) summary.notes.push(`Derived truck-treating BOL browser target from live Mongo: ${browserRoutes.delivery}`);',
2105
- ' else summary.notes.push("Could not derive truck-treating BOL browser route because live BOL context did not include both id_driver and id_bol.");',
2106
- ' if (primaryBolDelivered) summary.notes.push(`Live BOL ${primaryBolId} is already Delivered; direct bol-delivery route is an action route and may correctly redirect. QA should use delivered Open File/detail proof or create a localhost-only deliverable fixture from the fetched live context before testing Treat/Deliver actions.`);',
2107
- ' if (routeHints.length && routeHintMatched === false) summary.notes.push(`Ticket route hint(s) ${routeHints.join(", ")} did not match linked live BOL route(s): ${linkedRouteNames.join(", ") || "none"}. QA should use linked live BOL routes and report the mismatch as data evidence, not synthesize a route.`);',
2108
- ' summary.notes.push(`Seeded live truck-treating BOL context for ${bolIdentifiers.join(", ")}: ${bols.length} BOL(s), ${productionRoutes.length} production route(s), ${copiedTreatmentPlans.length} treatment plan(s).`);',
2109
- ' return [...bols, ...productionRoutes, ...productionDeliveries, ...truckTreatingDeliveries, ...copiedTreatmentPlans];',
2110
- '}',
2111
- '',
2112
- 'async function copyProductionInterchangeablesContext(sourceDb, targetDb, summary) {',
2113
- ' if (!shouldAutoDiscoverProductionInterchangeablesContext()) return [];',
2114
- ' const interchangeables = await copyQuery(sourceDb, targetDb, "chemical-interchangeables", { "id_chemicals.1": { $exists: true } }, summary, 12, { updatedAt: -1 });',
2115
- ' if (!interchangeables.length) {',
2116
- ' summary.notes.push("No live chemical-interchangeables records with multiple chemicals were available for Update Interchangeables QA.");',
2117
- ' return [];',
2118
- ' }',
2119
- ' const chemicalIds = unique(idValues(interchangeables, ["id_chemicals"]));',
2120
- ' const chemicals = await copyQuery(sourceDb, targetDb, "chemicals", { _id: { $in: chemicalIds }, types: "Finished Good" }, summary, 80, { name: 1 });',
2121
- ' const usableChemicalIds = unique(chemicals.map((doc) => doc._id));',
2122
- ' const items = await copyQuery(sourceDb, targetDb, "items", { $or: [{ id_chemical: { $in: usableChemicalIds } }, { _id: { $in: usableChemicalIds } }] }, summary, 120, { name: 1 });',
2123
- ' const itemIds = unique(items.map((doc) => doc._id));',
2124
- ' let treatmentPlans = await copyQuery(sourceDb, targetDb, "treatment-plans", { date_end: null, $or: [{ id_chemical: { $in: usableChemicalIds } }, { id_item: { $in: itemIds } }] }, summary, 120, { updatedAt: -1 });',
2125
- ' if (!treatmentPlans.length && usableChemicalIds.length) {',
2126
- ' treatmentPlans = await copyQuery(sourceDb, targetDb, "treatment-plans", { date_end: null, id_chemical: usableChemicalIds[0] }, summary, 40, { updatedAt: -1 });',
2127
- ' }',
2128
- ' const customerIds = unique(idValues(treatmentPlans, ["id_customer"]));',
2129
- ' const yardIds = unique(idValues(treatmentPlans, ["id_yard"]));',
2130
- ' const locationIds = unique(idValues(treatmentPlans, ["id_location"]));',
2131
- ' const wellGroupIds = unique(idValues(treatmentPlans, ["id_well_group"]));',
2132
- ' const tankIds = unique(collectNestedStringValues(treatmentPlans, ["id_tank"]));',
2133
- ' await copyByIds(sourceDb, targetDb, "customers", customerIds, summary, 80);',
2134
- ' await copyByIds(sourceDb, targetDb, "yards", yardIds, summary, 80);',
2135
- ' await copyByIds(sourceDb, targetDb, "production-locations", locationIds, summary, 120);',
2136
- ' await copyByIds(sourceDb, targetDb, "well-groups", wellGroupIds, summary, 120);',
2137
- ' await copyByIds(sourceDb, targetDb, "production-location-tanks", tankIds, summary, 120);',
2138
- ' if (yardIds.length) {',
2139
- ' const users = await targetDb.collection("users").find({}).project({ _id: 1, other: 1 }).limit(50).toArray().catch(() => []);',
2140
- ' for (const user of users) {',
2141
- ' const other = user && typeof user.other === "object" && user.other ? user.other : {};',
2142
- ' const nextYards = unique([...(Array.isArray(other.yards) ? other.yards : []), ...yardIds]);',
2143
- ' await targetDb.collection("users").updateOne({ _id: user._id }, { $set: { "other.yards": nextYards } });',
2144
- ' }',
2145
- ' if (users.length) summary.notes.push(`Added seeded Update Interchangeables yard ids to ${users.length} local QA user(s) so treatment-plan filters can return data.`);',
2146
- ' }',
2147
- ' summary.selected.qa_production_interchangeables_context = {',
2148
- ' interchangeable_ids: interchangeables.map((doc) => doc._id).filter(Boolean),',
2149
- ' chemical_ids: usableChemicalIds,',
2150
- ' item_ids: itemIds,',
2151
- ' treatment_plan_ids: treatmentPlans.map((doc) => doc._id).filter(Boolean),',
2152
- ' customer_ids: customerIds,',
2153
- ' yard_ids: yardIds,',
2154
- ' tank_ids: tankIds,',
2155
- ' browser_routes: { update_interchangeables: "/production-location/update-interchangeables" }',
2156
- ' };',
2157
- ' summary.notes.push(`Seeded Update Interchangeables context: ${interchangeables.length} interchangeable group(s), ${chemicals.length} chemical(s), ${items.length} active item(s), ${treatmentPlans.length} treatment plan(s).`);',
2158
- ' return [...interchangeables, ...chemicals, ...items, ...treatmentPlans];',
2159
- '}',
2160
- '',
2161
- 'async function copyPartInventorySnapshotContext(sourceDb, targetDb, summary) {',
2162
- ' if (!shouldSeedPartInventorySnapshotContext()) return [];',
2163
- ' const hints = extractPartInventorySnapshotHints();',
2164
- ' const snapshotOr = [];',
2165
- ' if (hints.partIds.length) {',
2166
- ' snapshotOr.push({ "metadata.id_part": { $in: hints.partIds } });',
2167
- ' snapshotOr.push({ id_part: { $in: hints.partIds } });',
2168
- ' }',
2169
- ' if (hints.dayKeys.length) snapshotOr.push({ day_key: { $in: hints.dayKeys } });',
2170
- ' const snapshotQuery = snapshotOr.length ? { section: "Parts", $or: snapshotOr } : { section: "Parts" };',
2171
- ' const snapshots = await copyQuery(sourceDb, targetDb, "inventory-daily-snapshots", snapshotQuery, summary, 120, { day_key: -1, updatedAt: -1 });',
2172
- ' const snapshotPartIds = unique([...hints.partIds, ...collectNestedStringValues(snapshots, ["id_part"])]);',
2173
- ' const snapshotDayKeys = unique([...hints.dayKeys, ...snapshots.map((doc) => String(doc && doc.day_key || "")).filter(Boolean)]);',
2174
- ' const checkinQuery = snapshotPartIds.length ? { "items.id": { $in: snapshotPartIds }, type: { $in: ["part", "manual_part"] } } : { type: { $in: ["part", "manual_part"] } };',
2175
- ' const checkoutQuery = snapshotPartIds.length ? { "items.id": { $in: snapshotPartIds }, type: "part" } : { type: "part" };',
2176
- ' const checkins = await copyQuery(sourceDb, targetDb, "checkins", checkinQuery, summary, 240, { date: -1, updatedAt: -1 });',
2177
- ' const checkouts = await copyQuery(sourceDb, targetDb, "checkouts", checkoutQuery, summary, 160, { date: -1, updatedAt: -1 });',
2178
- ' const yardIds = unique([',
2179
- ' ...hints.yardIds,',
2180
- ' ...idValues(checkins, ["id_yard"]),',
2181
- ' ...idValues(checkouts, ["id_warehouse_location"]),',
2182
- ' ...collectNestedStringValues(snapshots, ["id_yard"])',
2183
- ' ]);',
2184
- ' await copyByIds(sourceDb, targetDb, "yards", yardIds, summary, 80);',
2185
- ' if (!yardIds.length || /\\b(Kermit|Midland)\\b/i.test(readSeedHintText())) {',
2186
- ' await copyQuery(sourceDb, targetDb, "yards", { name: { $in: [/^Kermit$/i, /^Midland$/i] } }, summary, 20, { name: 1 });',
2187
- ' }',
2188
- ' if (snapshotPartIds.length) await copyByIds(sourceDb, targetDb, "items", snapshotPartIds, summary, 80);',
2189
- ' summary.selected.part_inventory_snapshot_context = {',
2190
- ' part_ids: snapshotPartIds,',
2191
- ' day_keys: snapshotDayKeys,',
2192
- ' yard_ids: yardIds,',
2193
- ' snapshot_count: snapshots.length,',
2194
- ' checkin_count: checkins.length,',
2195
- ' checkout_count: checkouts.length',
2196
- ' };',
2197
- ' if (snapshots.length || checkins.length || checkouts.length) {',
2198
- ' summary.notes.push(`Seeded Part inventory snapshot context: ${snapshots.length} saved snapshot row(s), ${checkins.length} checkin(s), ${checkouts.length} checkout(s), part ids=${snapshotPartIds.join(", ") || "none"}.`);',
2199
- ' } else {',
2200
- ' summary.notes.push("Part inventory snapshot context was requested by diagnosis proof, but no matching live snapshot/checkin/checkout documents were found.");',
2201
- ' }',
2202
- ' return [...snapshots, ...checkins, ...checkouts];',
2203
- '}',
2204
- '',
2205
- 'async function copyTicketAssetContext(sourceDb, targetDb, summary) {',
2206
- ' if (!shouldAutoDiscoverAssetContext()) return [];',
2207
- ' const assetIdentifiers = extractAssetIdentifiers();',
2208
- ' summary.selected.asset_identifiers = assetIdentifiers;',
2209
- ' const query = assetLookupQuery(assetIdentifiers);',
2210
- ' if (!query) return [];',
2211
- ' const assets = await copyQuery(sourceDb, targetDb, "assets", query, summary, 20, { updatedAt: -1, asset_number: 1 });',
2212
- ' if (!assets.length) {',
2213
- ' summary.notes.push(`No live assets matched ticket identifiers: ${assetIdentifiers.join(", ")}`);',
2214
- ' return [];',
2215
- ' }',
2216
- ' const yardIds = unique([',
2217
- ' ...idValues(assets, ["id_yard", "id_default_yard"]),',
2218
- ' ...collectNestedStringValues(assets, ["id", "id_yard"])',
2219
- ' ]);',
2220
- ' const locationIds = unique([',
2221
- ' ...idValues(assets, ["id_location", "id_current_location", "id_default_location"]),',
2222
- ' ...collectNestedStringValues(assets, ["id_location"])',
2223
- ' ]);',
2224
- ' const userIds = unique([',
2225
- ' ...idValues(assets, ["id_user", "id_driver", "id_created_by", "id_updated_by"]),',
2226
- ' ...collectNestedStringValues(assets, ["id_user", "id_driver", "id_created_by", "id_updated_by"])',
2227
- ' ]);',
2228
- ' await copyByIds(sourceDb, targetDb, "yards", yardIds, summary, 100);',
2229
- ' await copyByIds(sourceDb, targetDb, "production-locations", locationIds, summary, 100);',
2230
- ' await copyByIds(sourceDb, targetDb, "users", userIds, summary, 100);',
2231
- ' const assetIds = assets.map((doc) => doc._id).filter(Boolean);',
2232
- ' const assetNumbers = assets.map((doc) => String(doc.asset_number || doc.asset_number_string || doc.asset_unit_number || doc.unit_number || "")).filter(Boolean);',
2233
- ' summary.selected.qa_asset_context = {',
2234
- ' asset_ids: assetIds,',
2235
- ' asset_numbers: assetNumbers,',
2236
- ' yard_ids: yardIds,',
2237
- ' production_location_ids: locationIds,',
2238
- ' browser_routes: assetIds[0] ? { list: "/asset/list", detail: `/asset/detail/${assetIds[0]}`, edit: `/asset/edit/${assetIds[0]}` } : { list: "/asset/list" }',
2239
- ' };',
2240
- ' summary.notes.push(`Seeded live asset context for ${assetIdentifiers.join(", ")}: ${assets.length} asset(s), ${yardIds.length} yard id(s), ${locationIds.length} production location id(s).`);',
2241
- ' return assets;',
2242
- '}',
2243
- '',
2244
- 'async function ensureBillingSurchargePricingFixtures(targetDb, summary, serviceItems, customerIds) {',
2245
- ' const now = new Date();',
2246
- ' const activeServiceItems = (serviceItems || []).filter((doc) => {',
2247
- ' const typeText = String(doc && (doc.type || doc.category || doc.name || "") || "");',
2248
- ' return doc && doc._id && doc.active !== false && /misc|service|surcharge|fuel|delivery/i.test(typeText);',
2249
- ' });',
2250
- ' const fixtureItems = [];',
2251
- ' if (!activeServiceItems.length) {',
2252
- ' fixtureItems.push(',
2253
- ' { _id: "qa-misc-surcharge-item", name: "QA Misc Surcharge", description: "QA Misc Surcharge", type: "Misc", unit: "Each", active: true, createdAt: now, updatedAt: now },',
2254
- ' { _id: "qa-service-surcharge-item", name: "QA Service Surcharge", description: "QA Service Surcharge", type: "Service", unit: "Each", active: true, createdAt: now, updatedAt: now }',
2255
- ' );',
2256
- ' for (const item of fixtureItems) await targetDb.collection("items").replaceOne({ _id: item._id }, item, { upsert: true });',
2257
- ' summary.collections.items = (summary.collections.items || 0) + fixtureItems.length;',
2258
- ' summary.notes.push("Created localhost-only active service/misc surcharge item fixtures because the bounded live slice did not include one.");',
2259
- ' }',
2260
- ' const usableItems = activeServiceItems.length ? activeServiceItems : fixtureItems;',
2261
- ' const pricingDocs = [];',
2262
- ' for (const item of usableItems.slice(0, 4)) {',
2263
- ' const label = String(item.name || item.description || item._id || "QA Surcharge");',
2264
- ' const subtype = /service/i.test(String(item.type || label)) ? "service" : "misc";',
2265
- ' pricingDocs.push({',
2266
- ' _id: `qa-pricing-${subtype}-${String(item._id).replace(/[^a-z0-9_-]/gi, "-")}-global`,',
2267
- ' id_item: item._id, name: label, description: label, unit: item.unit || "Each", active: true, type: "sale", sub_type: subtype, price: 15, price_gross: 15, discount_percent: 0, id_customer: "", createdAt: now, updatedAt: now',
2268
- ' });',
2269
- ' if (customerIds && customerIds[0]) {',
2270
- ' pricingDocs.push({',
2271
- ' _id: `qa-pricing-${subtype}-${String(item._id).replace(/[^a-z0-9_-]/gi, "-")}-customer`,',
2272
- ' id_item: item._id, name: `${label} Customer Override`, description: `${label} Customer Override`, unit: item.unit || "Each", active: true, type: "sale", sub_type: subtype, price: 20, price_gross: 20, discount_percent: 0, id_customer: customerIds[0], createdAt: now, updatedAt: now',
2273
- ' });',
2274
- ' }',
2275
- ' }',
2276
- ' for (const doc of pricingDocs) await targetDb.collection("pricing-items").replaceOne({ _id: doc._id }, doc, { upsert: true });',
2277
- ' summary.collections["pricing-items"] = (summary.collections["pricing-items"] || 0) + pricingDocs.length;',
2278
- ' summary.selected.qa_surcharge_fixture_items = usableItems.map((doc) => doc._id);',
2279
- ' summary.selected.qa_surcharge_pricing_items = pricingDocs.map((doc) => doc._id);',
2280
- ' if (pricingDocs.length) summary.notes.push(`Ensured ${pricingDocs.length} local pricing-items for service/misc surcharge override/default QA.`);',
2281
- '}',
2282
- '',
2283
- 'async function cleanupSyntheticBillingDashboardQaFixtures(targetDb, summary) {',
2284
- ' const fixtureIdQuery = { _id: /^qa-billing-/ };',
2285
- ' const fixtureCollections = ["customers", "yards", "items", "production-locations", "bols", "production-sales-orders", "production-deliveries", "invoices"];',
2286
- ' let removed = 0;',
2287
- ' for (const collectionName of fixtureCollections) {',
2288
- ' const result = await targetDb.collection(collectionName).deleteMany(fixtureIdQuery).catch(() => ({ deletedCount: 0 }));',
2289
- ' removed += Number(result && result.deletedCount || 0);',
2290
- ' }',
2291
- ' const invoiceResult = await targetDb.collection("invoices").deleteMany({ $or: [{ invoice_string: /^QA-INV-/ }, { invoice_number: 4333 }] }).catch(() => ({ deletedCount: 0 }));',
2292
- ' removed += Number(invoiceResult && invoiceResult.deletedCount || 0);',
2293
- ' const psoResult = await targetDb.collection("production-sales-orders").deleteMany({ $or: [{ order_number_string: /^QA-PSO-/ }, { activity_number: /^QA-PSO-/ }] }).catch(() => ({ deletedCount: 0 }));',
2294
- ' removed += Number(psoResult && psoResult.deletedCount || 0);',
2295
- ' if (removed) summary.notes.push(`Removed ${removed} stale synthetic billing dashboard QA fixture document(s) from the local Mongo profile.`);',
2296
- '}',
2297
- '',
2298
- 'async function ensureBillingDashboardQaFixtures(targetDb, summary, customerIds, yardIds, serviceItems) {',
2299
- ' const now = new Date();',
2300
- ' const customerId = (customerIds && customerIds[0]) || "qa-billing-customer";',
2301
- ' const yardId = (yardIds && yardIds[0]) || "qa-billing-yard";',
2302
- ' const item = (serviceItems || []).find((doc) => doc && doc._id) || { _id: "qa-billing-service-item", name: "QA Billing Service Item", unit: "Each", type: "Service", active: true };',
2303
- ' const locationId = "qa-billing-location";',
2304
- ' const bolId = "qa-billing-bol";',
2305
- ' const psoId = "qa-billing-pso-awaiting-invoice";',
2306
- ' const deliveryId = "qa-billing-delivery-awaiting-invoice";',
2307
- ' const invoiceId = "qa-billing-existing-processing-invoice";',
2308
- ' await targetDb.collection("customers").replaceOne({ _id: customerId }, {',
2309
- ' _id: customerId, name: "QA Billing Customer", active: true, send_type: "Email", customer_approval_required: false, createdAt: now, updatedAt: now',
2310
- ' }, { upsert: true });',
2311
- ' await targetDb.collection("yards").replaceOne({ _id: yardId }, { _id: yardId, name: "QA Billing Yard", active: true, createdAt: now, updatedAt: now }, { upsert: true });',
2312
- ' await targetDb.collection("items").replaceOne({ _id: item._id }, { ...item, _id: item._id, name: item.name || "QA Billing Service Item", unit: item.unit || "Each", type: item.type || "Service", active: true, updatedAt: now }, { upsert: true });',
2313
- ' await targetDb.collection("production-locations").replaceOne({ _id: locationId }, {',
2314
- ' _id: locationId, id_customer: customerId, customer: "QA Billing Customer", location: "QA Billing Location", id_yard: yardId, county: "QA County", active: true, createdAt: now, updatedAt: now',
2315
- ' }, { upsert: true });',
2316
- ' await targetDb.collection("bols").replaceOne({ _id: bolId }, {',
2317
- ' _id: bolId, bol_string: "QA-BOL-004333", bol_number: "QA-BOL-004333", status: "Delivered", date: now, driver: "QA Driver", createdAt: now, updatedAt: now',
2318
- ' }, { upsert: true });',
2319
- ' const lineItem = {',
2320
- ' _id: "qa-billing-pso-line-1", id_item: item._id, item: item.name || "QA Billing Service Item", item_type: item.type || "Service", type: item.type || "Service", quantity: 2, unit: item.unit || "Each", price: 100, price_subtotal: 200, price_total: 216.5, tax_amount: 16.5, billed: true, memo_bill: false, taxable: true, id_location: locationId, location: "QA Billing Location", id_yard: yardId, yard: "QA Billing Yard"',
2321
- ' };',
2322
- ' await targetDb.collection("production-sales-orders").replaceOne({ _id: psoId }, {',
2323
- ' _id: psoId, status: "Awaiting Invoice", type: "Invoice", id_customer: customerId, customer: "QA Billing Customer", id_default_yard: yardId, date_created: now, date_ship: now, date_needed: now, order_number_string: "QA-PSO-004333", activity_number: "QA-PSO-004333", bol_numbers: "QA-BOL-004333", drivers: "QA Driver", ship_to_type: "Location", ship_to_location: "QA Billing Location", price: 200, price_subtotal: 200, account_manager: "QA Admin", user_approved: "QA Admin", production_locations: [{ id_location: locationId, location: "QA Billing Location", items: [lineItem] }], createdAt: now, updatedAt: now',
2324
- ' }, { upsert: true });',
2325
- ' await targetDb.collection("production-deliveries").replaceOne({ _id: deliveryId }, {',
2326
- ' _id: deliveryId, type: "Delivery", approved: true, billed: true, memo_bill: false, invoiced: false, generic: false, invoices: [], consignment: false, id_activity: psoId, activity_number: "QA-PSO-004333", activity_type: "Invoice", id_customer: customerId, id_location: locationId, location: "QA Billing Location", id_yard: yardId, yard: "QA Billing Yard", id_item: item._id, item: item.name || "QA Billing Service Item", item_type: item.type || "Service", quantity: 2, unit: item.unit || "Each", price_per_unit: 100, price_subtotal: 200, price_total: 216.5, tax_amount: 16.5, id_bol: bolId, bol_number: "QA-BOL-004333", date: now, date_ship: now, user_approved: "QA Admin", account_manager: "QA Admin", createdAt: now, updatedAt: now',
2327
- ' }, { upsert: true });',
2328
- ' await targetDb.collection("invoices").replaceOne({ _id: invoiceId }, {',
2329
- ' _id: invoiceId, status: "Processing", type: "Invoice", id_customer: customerId, customer: "QA Billing Customer", id_yard: yardId, yard: "QA Billing Yard", invoice_string: "QA-INV-004333", invoice_number: 4333, date_invoice: now, total: 216.5, subtotal: 200, tax_amount: 16.5, printed: false, invoice_opened: false, logs: [], files: [], line_items: [{ ...lineItem, source_production_delivery_id: deliveryId, description: lineItem.item, date: now }], createdAt: now, updatedAt: now',
2330
- ' }, { upsert: true });',
2331
- ' summary.collections.customers = (summary.collections.customers || 0) + 1;',
2332
- ' summary.collections.yards = (summary.collections.yards || 0) + 1;',
2333
- ' summary.collections.items = (summary.collections.items || 0) + 1;',
2334
- ' summary.collections["production-locations"] = (summary.collections["production-locations"] || 0) + 1;',
2335
- ' summary.collections.bols = (summary.collections.bols || 0) + 1;',
2336
- ' summary.collections["production-sales-orders"] = (summary.collections["production-sales-orders"] || 0) + 1;',
2337
- ' summary.collections["production-deliveries"] = (summary.collections["production-deliveries"] || 0) + 1;',
2338
- ' summary.collections.invoices = (summary.collections.invoices || 0) + 1;',
2339
- ' summary.selected.qa_billing_fixture = { customerId, yardId, psoId, deliveryId, invoiceId, itemId: item._id };',
2340
- ' summary.notes.push("Ensured localhost-only Billing Dashboard fixtures: one existing processing invoice plus one awaiting-invoice PSO/delivery source row.");',
2341
- '}',
2342
- '',
2343
- 'async function selectDashboardReadyProductionDeliveryIds(sourceDb, query, limit = 5) {',
2344
- ' const rows = await sourceDb.collection("production-deliveries").aggregate([',
2345
- ' { $match: query || {} },',
2346
- ' { $lookup: { from: "production-sales-orders", localField: "id_activity", foreignField: "_id", as: "dbPSO" } },',
2347
- ' { $unwind: { path: "$dbPSO", preserveNullAndEmptyArrays: false } },',
2348
- ' { $match: { "dbPSO.type": { $ne: "Consignment Invoice" } } },',
2349
- ' { $lookup: {',
2350
- ' from: "bols",',
2351
- ' let: { idBol: "$id_bol" },',
2352
- ' pipeline: [{ $match: { $expr: { $eq: ["$_id", "$$idBol"] }, status: "Delivered" } }],',
2353
- ' as: "bol"',
2354
- ' } },',
2355
- ' { $unwind: { path: "$bol", preserveNullAndEmptyArrays: false } },',
2356
- ' { $lookup: { from: "production-locations", localField: "id_location", foreignField: "_id", as: "dbProductionLocation" } },',
2357
- ' { $lookup: { from: "well-groups", localField: "id_well_group", foreignField: "_id", as: "dbWellGroup" } },',
2358
- ' { $match: { $or: [{ "dbProductionLocation.0": { $exists: true } }, { "dbWellGroup.0": { $exists: true } }] } },',
2359
- ' { $sort: { date: -1, date_ship: -1, date_treated: -1, updatedAt: -1 } },',
2360
- ' { $limit: limit },',
2361
- ' { $project: { _id: 1 } }',
2362
- ' ]).toArray();',
2363
- ' return rows.map((doc) => doc._id).filter(Boolean);',
2364
- '}',
2365
- '',
2366
- 'async function selectTruckTreatDashboardReadyProductionDeliveryIds(sourceDb, query, limit = 5) {',
2367
- ' const rows = await sourceDb.collection("production-deliveries").aggregate([',
2368
- ' { $match: query || {} },',
2369
- ' { $lookup: {',
2370
- ' from: "bols",',
2371
- ' let: { idBol: "$id_bol" },',
2372
- ' pipeline: [{ $match: { $expr: { $eq: ["$_id", "$$idBol"] }, status: "Delivered" } }],',
2373
- ' as: "bol"',
2374
- ' } },',
2375
- ' { $unwind: { path: "$bol", preserveNullAndEmptyArrays: false } },',
2376
- ' { $lookup: { from: "production-locations", localField: "id_location", foreignField: "_id", as: "dbProductionLocation" } },',
2377
- ' { $lookup: { from: "well-groups", localField: "id_well_group", foreignField: "_id", as: "dbWellGroup" } },',
2378
- ' { $match: { $or: [{ "dbProductionLocation.0": { $exists: true } }, { "dbWellGroup.0": { $exists: true } }] } },',
2379
- ' { $sort: { date: -1, date_treated: -1, updatedAt: -1 } },',
2380
- ' { $limit: limit },',
2381
- ' { $project: { _id: 1 } }',
2382
- ' ]).toArray();',
2383
- ' return rows.map((doc) => doc._id).filter(Boolean);',
2384
- '}',
2385
- '',
2386
- 'async function selectLocalhostDeliveryFixtureIds(sourceDb, query, limit = 3) {',
2387
- ' const rows = await sourceDb.collection("production-deliveries").aggregate([',
2388
- ' { $match: { $and: [query || {}, { id_bol: { $type: "string", $ne: "" } }] } },',
2389
- ' { $lookup: { from: "production-sales-orders", localField: "id_activity", foreignField: "_id", as: "dbPSO" } },',
2390
- ' { $unwind: { path: "$dbPSO", preserveNullAndEmptyArrays: false } },',
2391
- ' { $match: { "dbPSO.type": { $ne: "Consignment Invoice" } } },',
2392
- ' { $lookup: { from: "production-locations", localField: "id_location", foreignField: "_id", as: "dbProductionLocation" } },',
2393
- ' { $lookup: { from: "well-groups", localField: "id_well_group", foreignField: "_id", as: "dbWellGroup" } },',
2394
- ' { $match: { $or: [{ "dbProductionLocation.0": { $exists: true } }, { "dbWellGroup.0": { $exists: true } }] } },',
2395
- ' { $sort: { date: -1, date_ship: -1, updatedAt: -1 } },',
2396
- ' { $limit: limit },',
2397
- ' { $project: { _id: 1 } }',
2398
- ' ]).toArray();',
2399
- ' return rows.map((doc) => doc._id).filter(Boolean);',
2400
- '}',
2401
- '',
2402
- 'async function seedBillingInventory(sourceDb, targetDb) {',
2403
- ' const includeBillingFixtures = shouldSeedBillingDashboardFixtures();',
2404
- ' const includePricingImportFixtures = includeBillingFixtures || shouldSeedPricingImportFixtures();',
2405
- ' const summary = { profile: desiredSeedProfile(), collections: {}, selected: {}, notes: [] };',
2406
- ' const identifiers = extractSeedIdentifiers();',
2407
- ' summary.selected.seed_identifiers = identifiers;',
2408
- ' const qaIdentityUsers = await copyQaIdentityUsers(sourceDb, targetDb, summary);',
2409
- ' const truckTreatingBolContextDocs = await copyTruckTreatingBolContext(sourceDb, targetDb, summary);',
2410
- ' const ticketAssetContextDocs = await copyTicketAssetContext(sourceDb, targetDb, summary);',
2411
- ' const productionInterchangeablesContextDocs = await copyProductionInterchangeablesContext(sourceDb, targetDb, summary);',
2412
- ' const partInventorySnapshotContextDocs = await copyPartInventorySnapshotContext(sourceDb, targetDb, summary);',
2413
- ' const pricingImportWorkbookContextDocs = includePricingImportFixtures ? await copyPricingImportWorkbookContext(sourceDb, targetDb, summary) : [];',
2414
- ' if (!includeBillingFixtures) {',
2415
- ' summary.notes.push("Billing dashboard fixtures are disabled because ticket context does not require billing, invoices, taxes, inventory, surcharges, checkout, or pick tickets.");',
2416
- ' await cleanupSyntheticBillingDashboardQaFixtures(targetDb, summary);',
2417
- ' }',
2418
- ' if (includePricingImportFixtures && !includeBillingFixtures) {',
2419
- ' summary.notes.push("Using pricing-import seed profile: pricing records are copied without synthetic billing dashboard invoice fixtures.");',
2420
- ' }',
2421
- ' const billableDeliveryQuery = { $and: [',
2422
- ' { $or: [{ type: "Delivery" }, { type: "Pickup" }] },',
2423
- ' { $or: [{ consignment: { $exists: false } }, { consignment: false }] },',
2424
- ' { approved: true }, { invoiced: false }, { generic: false }, { "invoices.0": { $exists: false } },',
2425
- ' { $or: [{ billed: true }, { memo_bill: true }] }',
2426
- ' ] };',
2427
- ' const billableTruckTreatQuery = { $and: [',
2428
- ' { type: "Truck Treat" }, { invoiced: false }, { generic: false }, { approved: true }, { "invoices.0": { $exists: false } },',
2429
- ' { $or: [{ billed: true }, { memo_bill: true }] }',
2430
- ' ] };',
2431
- ' const hintedQuery = identifierQuery(identifiers);',
2432
- ' let hintedProductionDeliveries = [];',
2433
- ' let hintedTruckTreatingDeliveries = [];',
2434
- ' if (includeBillingFixtures && hintedQuery) {',
2435
- ' hintedProductionDeliveries = await copyByIds(sourceDb, targetDb, "production-deliveries", await selectDashboardReadyProductionDeliveryIds(sourceDb, { $and: [billableDeliveryQuery, hintedQuery] }, 3), summary, 3);',
2436
- ' hintedTruckTreatingDeliveries = await copyByIds(sourceDb, targetDb, "production-deliveries", await selectTruckTreatDashboardReadyProductionDeliveryIds(sourceDb, { $and: [billableTruckTreatQuery, hintedQuery] }, 3), summary, 3);',
2437
- ' if (hintedProductionDeliveries.length || hintedTruckTreatingDeliveries.length) summary.notes.push(`Preferred live records matching ticket identifiers: ${identifiers.join(", ")}`);',
2438
- ' else summary.notes.push(`No billable live records matched ticket identifiers: ${identifiers.join(", ")}`);',
2439
- ' }',
2440
- '',
2441
- ' let fallbackProductionDeliveries = includeBillingFixtures && hintedProductionDeliveries.length ? [] : includeBillingFixtures ? await copyByIds(sourceDb, targetDb, "production-deliveries", await selectDashboardReadyProductionDeliveryIds(sourceDb, billableDeliveryQuery, 3), summary, 3) : [];',
2442
- ' if (includeBillingFixtures && !hintedProductionDeliveries.length && !fallbackProductionDeliveries.length) {',
2443
- ' fallbackProductionDeliveries = await copyByIds(sourceDb, targetDb, "production-deliveries", await selectLocalhostDeliveryFixtureIds(sourceDb, billableDeliveryQuery, 3), summary, 3);',
2444
- ' if (fallbackProductionDeliveries.length) summary.notes.push("Created localhost-only delivery invoice fixture from live delivery data by normalizing copied BOL status to Delivered in local Mongo.");',
2445
- ' }',
2446
- ' const fallbackTruckTreatingDeliveries = includeBillingFixtures && hintedTruckTreatingDeliveries.length ? [] : includeBillingFixtures ? await copyByIds(sourceDb, targetDb, "production-deliveries", await selectTruckTreatDashboardReadyProductionDeliveryIds(sourceDb, billableTruckTreatQuery, 3), summary, 3) : [];',
2447
- ' const productionDeliveries = [...hintedProductionDeliveries, ...fallbackProductionDeliveries];',
2448
- ' const truckTreatingDeliveries = [...hintedTruckTreatingDeliveries, ...fallbackTruckTreatingDeliveries];',
2449
- ' summary.selected.production_deliveries = productionDeliveries.map((doc) => doc._id);',
2450
- ' summary.selected.truck_treating_deliveries = truckTreatingDeliveries.map((doc) => doc._id);',
2451
- ' if (!productionDeliveries.length && !truckTreatingDeliveries.length) {',
2452
- ' summary.notes.push(includeBillingFixtures ? "No live billable delivery or truck-treatment records matched the Billing Dashboard awaiting-invoice filters." : "Skipped billable delivery/truck-treatment discovery because billing fixtures are disabled for this QA scope.");',
2453
- ' }',
2454
- '',
2455
- ' const sourceDocs = [...qaIdentityUsers, ...truckTreatingBolContextDocs, ...ticketAssetContextDocs, ...productionInterchangeablesContextDocs, ...partInventorySnapshotContextDocs, ...pricingImportWorkbookContextDocs, ...productionDeliveries, ...truckTreatingDeliveries];',
2456
- ' const pricingImportContext = summary.selected.pricing_import_workbook_context || {};',
2457
- ' const customerIds = unique([...idValues(sourceDocs, ["id_customer"]), ...(Array.isArray(pricingImportContext.customer_ids) ? pricingImportContext.customer_ids : [])]);',
2458
- ' const locationIds = idValues(sourceDocs, ["id_location"]);',
2459
- ' const wellGroupIds = idValues(sourceDocs, ["id_well_group"]);',
2460
- ' const itemIds = unique([...idValues(sourceDocs, ["id_item", "id_chemical"]), ...(Array.isArray(pricingImportContext.item_ids) ? pricingImportContext.item_ids : []), ...(Array.isArray(pricingImportContext.chemical_ids) ? pricingImportContext.chemical_ids : [])]);',
2461
- ' const bolIds = idValues(sourceDocs, ["id_bol", "id_bols"]);',
2462
- ' const yardIds = idValues(sourceDocs, ["id_yard"]);',
2463
- ' const activityIds = idValues(sourceDocs, ["id_activity", "id_production_sales_order", "id_sales_order"]);',
2464
- ' const tankIds = idValues(sourceDocs, ["id_tank"]);',
2465
- ' const truckTreatingDetailIds = idValues(sourceDocs, ["id_delivery", "id_truck_treating_delivery"]);',
2466
- ' const userIds = idValues(sourceDocs, ["id_user_approved", "id_driver", "id_driver_scheduled", "id_account_manager", "id_foreman"]);',
2467
- '',
2468
- ' const copiedLocations = await copyByIds(sourceDb, targetDb, "production-locations", locationIds, summary);',
2469
- ' const copiedWellGroups = await copyByIds(sourceDb, targetDb, "well-groups", wellGroupIds, summary);',
2470
- ' const copiedItems = await copyByIds(sourceDb, targetDb, "items", itemIds, summary);',
2471
- ' const copiedChemicals = await copyByIds(sourceDb, targetDb, "chemicals", itemIds, summary);',
2472
- ' await copyByIds(sourceDb, targetDb, "production-sales-orders", activityIds, summary);',
2473
- ' await copyByIds(sourceDb, targetDb, "production-location-tanks", tankIds, summary);',
2474
- ' await copyByIds(sourceDb, targetDb, "truck-treating-deliveries", truckTreatingDetailIds, summary);',
2475
- ' await copyByIds(sourceDb, targetDb, "customers", unique([...customerIds, ...idValues(copiedLocations, ["id_customer"]), ...idValues(copiedWellGroups, ["id_customer"])]), summary);',
2476
- ' await copyByIds(sourceDb, targetDb, "bols", bolIds, summary);',
2477
- ' if (productionDeliveries.length && bolIds.length) await targetDb.collection("bols").updateMany({ _id: { $in: bolIds } }, { $set: { status: "Delivered" } });',
2478
- ' await copyByIds(sourceDb, targetDb, "yards", yardIds, summary);',
2479
- ' await copyByIds(sourceDb, targetDb, "users", userIds, summary);',
2480
- '',
2481
- ' const copiedServiceItems = await copyQuery(sourceDb, targetDb, "items", { $or: [',
2482
- ' { type: /service/i }, { type: /misc/i }, { category: /service/i }, { category: /misc/i },',
2483
- ' { name: /surcharge|fuel|delivery|service|misc/i }',
2484
- ' ] }, summary, 40, { updatedAt: -1 });',
2485
- ' summary.selected.service_or_surcharge_items = copiedServiceItems.map((doc) => doc._id);',
2486
- '',
2487
- ' if (includePricingImportFixtures) {',
2488
- ' await copyQuery(sourceDb, targetDb, "pricing-items", { $or: [',
2489
- ' { id_item: { $in: unique([...itemIds, ...copiedItems.map((doc) => doc._id), ...copiedServiceItems.map((doc) => doc._id)]) } },',
2490
- ' { id_chemical: { $in: unique([...itemIds, ...copiedChemicals.map((doc) => doc._id)]) } },',
2491
- ' { id_customer: { $in: customerIds } },',
2492
- ' { sub_type: { $in: ["misc", "service"] } },',
2493
- ' { id_item: { $in: copiedServiceItems.map((doc) => doc._id) } },',
2494
- ' { name: /surcharge|fuel|delivery|service|misc/i }',
2495
- ' ] }, summary, 80, { updatedAt: -1 });',
2496
- ' }',
2497
- '',
2498
- ' if (includeBillingFixtures) {',
2499
- ' await copyQuery(sourceDb, targetDb, "sales-taxes", {}, summary, 80, { updatedAt: -1 });',
2500
- ' await copyQuery(sourceDb, targetDb, "state-counties", {}, summary, 80, { updatedAt: -1 });',
2501
- ' await copyQuery(sourceDb, targetDb, "accounting-codes", {}, summary, 80, { updatedAt: -1 });',
2502
- ' await ensureBillingSurchargePricingFixtures(targetDb, summary, copiedServiceItems, customerIds);',
2503
- ' await ensureBillingDashboardQaFixtures(targetDb, summary, customerIds, yardIds, copiedServiceItems);',
2504
- '',
2505
- ' const inventoryLocationQuery = { $or: [',
2506
- ' { id_item: { $in: unique([...itemIds, ...copiedItems.map((doc) => doc._id), ...copiedServiceItems.map((doc) => doc._id)]) } },',
2507
- ' { id_chemical: { $in: unique([...itemIds, ...copiedChemicals.map((doc) => doc._id)]) } },',
2508
- ' { id_yard: { $in: yardIds } }',
2509
- ' ] };',
2510
- ' const inventoryLocations = await copyQuery(sourceDb, targetDb, "inventory-locations", inventoryLocationQuery, summary, 80, { updatedAt: -1 });',
2511
- ' await copyQuery(sourceDb, targetDb, "inventory-transactions", { $or: [',
2512
- ' { id_inventory_location: { $in: inventoryLocations.map((doc) => doc._id) } },',
2513
- ' { id_item: { $in: itemIds } },',
2514
- ' { id_chemical: { $in: itemIds } }',
2515
- ' ] }, summary, 120, { date: -1, updatedAt: -1 });',
2516
- ' }',
2517
- '',
2518
- ' summary.ready = pricingImportWorkbookContextDocs.length > 0 || truckTreatingBolContextDocs.length > 0 || ticketAssetContextDocs.length > 0 || productionInterchangeablesContextDocs.length > 0 || partInventorySnapshotContextDocs.length > 0 || productionDeliveries.length > 0 || truckTreatingDeliveries.length > 0;',
2519
- ' return summary;',
2520
- '}',
2521
- '',
2522
1406
  '(async () => {',
2523
- ' const { MongoClient } = requireMongo();',
2524
- ' const source = resolveRuntimeSource();',
2525
- ' const targetUri = process.env.RESOLVEIO_SUPPORT_QA_MONGO_URL || process.env.RESOLVEIO_RUNNER_QA_MONGO_URL || process.env.MONGO_URL || "mongodb://127.0.0.1:3001/resolveio?directConnection=true";',
2526
- ' if (!source.uri) {',
2527
- ' preserveExistingSeedResult("missing_live_mongo_uri");',
2528
- ' writeResult({ status: isLiveDataRequired() ? "failed" : "skipped", reason: "missing_live_mongo_uri", required: isLiveDataRequired(), result_path: resultPath }, isLiveDataRequired() ? 5 : 0);',
2529
- ' }',
2530
- ' if (!isLocalMongoUri(targetUri)) writeResult({ status: "failed", reason: "target_mongo_must_be_localhost", target_uri_redacted: redactUri(targetUri), result_path: resultPath }, 3);',
2531
- ' if (String(source.uri) === String(targetUri)) writeResult({ status: "failed", reason: "source_and_target_mongo_match", result_path: resultPath }, 3);',
2532
- ' await waitForTargetMongo(MongoClient, targetUri);',
2533
- ' const sourceClient = new MongoClient(source.uri, { readPreference: "secondaryPreferred", serverSelectionTimeoutMS: 15000 });',
2534
- ' const targetClient = new MongoClient(targetUri, { serverSelectionTimeoutMS: 15000 });',
2535
- ' try {',
2536
- ' await sourceClient.connect();',
2537
- ' await targetClient.connect();',
2538
- ' const sourceDb = source.database ? sourceClient.db(source.database) : sourceClient.db();',
2539
- ' const targetDb = targetClient.db();',
2540
- ' const summary = await seedBillingInventory(sourceDb, targetDb);',
2541
- ' writeResult({',
2542
- ' status: summary.ready ? "pass" : "needs-data",',
2543
- ' source_uri_redacted: redactUri(source.uri),',
2544
- ' target_uri_redacted: redactUri(targetUri),',
2545
- ' ...summary,',
2546
- ' result_path: resultPath',
2547
- ' }, summary.ready ? 0 : 4);',
2548
- ' } finally {',
2549
- ' await sourceClient.close().catch(() => undefined);',
2550
- ' await targetClient.close().catch(() => undefined);',
2551
- ' }',
1407
+ ' const { MongoClient } = requireMongo(); const source = resolveRuntimeSource(); const targetUri = process.env.RESOLVEIO_SUPPORT_QA_MONGO_URL || process.env.RESOLVEIO_RUNNER_QA_MONGO_URL || process.env.MONGO_URL || "mongodb://127.0.0.1:3001/resolveio?directConnection=true";',
1408
+ ' if (!source.uri) { preserveExistingSeedResult("missing_live_mongo_uri"); writeResult({ status: isLiveDataRequired() ? "failed" : "skipped", reason: "missing_live_mongo_uri", required: isLiveDataRequired(), result_path: resultPath }, isLiveDataRequired() ? 5 : 0); }',
1409
+ ' if (!isLocalMongoUri(targetUri)) writeResult({ status: "failed", reason: "target_mongo_must_be_localhost", target_uri_redacted: redactUri(targetUri), result_path: resultPath }, 3); if (String(source.uri) === String(targetUri)) writeResult({ status: "failed", reason: "source_and_target_mongo_match", result_path: resultPath }, 3); await waitForTargetMongo(MongoClient, targetUri);',
1410
+ ' const sourceClient = new MongoClient(source.uri, { readPreference: "secondaryPreferred", serverSelectionTimeoutMS: 15000 }); const targetClient = new MongoClient(targetUri, { serverSelectionTimeoutMS: 15000 });',
1411
+ ' try { await sourceClient.connect(); await targetClient.connect(); const sourceDb = source.database ? sourceClient.db(source.database) : sourceClient.db(); const targetDb = targetClient.db(); const summary = await seedSupportLiveContext(sourceDb, targetDb); const seedRequired = isLiveDataRequired(); const seedStatus = summary.ready ? "pass" : (seedRequired ? "failed" : "needs-data"); writeResult({ status: seedStatus, required: seedRequired, source_uri_redacted: redactUri(source.uri), target_uri_redacted: redactUri(targetUri), ...summary, result_path: resultPath }, summary.ready || !seedRequired ? 0 : 4); } finally { await sourceClient.close().catch(() => undefined); await targetClient.close().catch(() => undefined); }',
2552
1412
  '})().catch((error) => writeResult({ status: "failed", reason: error && (error.stack || error.message) || String(error), result_path: resultPath }, 2));',
2553
1413
  ''
2554
1414
  ].join('\n');
@@ -2717,11 +1577,8 @@ function buildResolveIORunnerBugfixComparisonQaScript() {
2717
1577
  ''
2718
1578
  ].join('\n');
2719
1579
  }
2720
- function buildResolveIOPartInventorySnapshotProofScriptLines() {
2721
- return String.raw(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\nfunction activeMatrixRow() {\n\tconst matrix = readJson(matrixPath) || {};\n\tconst rows = matrixRows(matrix);\n\treturn rows.find((candidate) => !/^(pass|passed)$/i.test(String(candidate && candidate.status || \"\"))) || rows[0] || {};\n}\nfunction readSeedPartInventorySnapshotContext() {\n\tconst seed = readJson(path.join(artifactDir, \"qa-live-data-seed-result.json\")) || {};\n\tconst selected = seed && seed.selected || {};\n\tconst context = selected.part_inventory_snapshot_context || selected.partInventorySnapshotContext || null;\n\tif (!context || typeof context !== \"object\") return null;\n\tconst partIds = uniqueStrings(context.part_ids || context.partIds || []);\n\tconst dayKeys = uniqueStrings(context.day_keys || context.dayKeys || []);\n\tif (!partIds.length && !dayKeys.length && !Number(context.snapshot_count || context.snapshotCount || 0)) return null;\n\treturn { ...context, part_ids: partIds, day_keys: dayKeys };\n}\nfunction shouldRunPartInventorySnapshotProof() {\n\tconst context = readSeedPartInventorySnapshotContext();\n\tif (!context) return false;\n\tconst text = [activeRowText(), targetRoute].join(\"\n\");\n\treturn /\b(reportDailyInventorySnapshot|inventory-daily-snapshots|dailys+inventorys+snapshot|parts+inventorys+snapshot|warehouse_location|Parts)\b/i.test(text);\n}\nfunction normalizePartSnapshotDayKey(value) {\n\tconst text = String(value || \"\").trim();\n\tconst match = /d{4}-d{2}-d{2}/.exec(text);\n\tif (match) return match[0];\n\tconst date = new Date(text);\n\treturn Number.isFinite(date.getTime()) ? date.toISOString().slice(0, 10) : \"\";\n}\nfunction partSnapshotRowPartId(row) {\n\tconst metadata = row && typeof row.metadata === \"object\" ? row.metadata : {};\n\treturn String(metadata.id_part || row.id_part || row.id_item || row.id_source || \"\").trim();\n}\nfunction partSnapshotRowYardId(row) {\n\tconst metadata = row && typeof row.metadata === \"object\" ? row.metadata : {};\n\tconst explicit = String(metadata.id_yard || row.id_yard || row.id_warehouse_location || \"\").trim();\n\tif (explicit) return explicit;\n\tconst source = String(row && row.id_source || \"\");\n\tconst sourceMatch = /^yard:([A-Za-z0-9_-]+)/i.exec(source);\n\treturn sourceMatch ? sourceMatch[1] : \"\";\n}\nfunction normalizeWarehouseName(value) {\n\treturn String(value || \"\").trim().toLowerCase().replace(/s+/g, \" \");\n}\nfunction methodDateFromDayKey(dayKey) {\n\treturn new Date(dayKey + \"T12:00:00.000Z\");\n}\nfunction resolveWsUrl() {\n\tif (/^https:/i.test(serverUrl)) return serverUrl.replace(/^https:/i, \"wss:\");\n\tif (/^http:/i.test(serverUrl)) return serverUrl.replace(/^http:/i, \"ws:\");\n\treturn serverUrl;\n}\nfunction callResolveIOMethodViaWs(auth, route, methodName, args) {\n\treturn new Promise((resolve, reject) => {\n\t\tconst token = auth && auth.accessToken;\n\t\tif (!token) {\n\t\t\treject(new Error(\"Missing access token for websocket method proof.\"));\n\t\t\treturn;\n\t\t}\n\t\tconst WebSocket = requireQaModule(\"ws\");\n\t\tconst { unpack } = requireQaModule(\"msgpackr\");\n\t\tconst ws = new WebSocket(resolveWsUrl(), [token], { origin: clientUrl });\n\t\tconst messageId = Date.now() + Math.floor(Math.random() * 100000);\n\t\tconst timeout = setTimeout(() => {\n\t\t\ttry { ws.close(); } catch (error) {}\n\t\t\treject(new Error(\"Timed out waiting for \" + methodName + \" websocket response.\"));\n\t\t}, Number(process.env.RESOLVEIO_SUPPORT_QA_METHOD_PROOF_TIMEOUT_MS || process.env.RESOLVEIO_RUNNER_QA_METHOD_PROOF_TIMEOUT_MS || 30000));\n\t\tlet settled = false;\n\t\tconst finish = (error, value) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timeout);\n\t\t\ttry { ws.close(); } catch (closeError) {}\n\t\t\tif (error) reject(error);\n\t\t\telse resolve(value);\n\t\t};\n\t\tws.on(\"open\", () => {\n\t\t\tconst payload = [[route || \"/\", new Date().toISOString(), messageId, \"method\", methodName, ...(args || [])]];\n\t\t\tws.send(JSON.stringify(payload));\n\t\t});\n\t\tws.on(\"message\", (raw) => {\n\t\t\tlet messages = [];\n\t\t\tconst text = Buffer.isBuffer(raw) ? raw.toString(\"utf8\") : String(raw || \"\");\n\t\t\tif (text === \"ping\") {\n\t\t\t\ttry { ws.send(\"pong\"); } catch (error) {}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst decoded = Buffer.isBuffer(raw) ? unpack(raw) : JSON.parse(text);\n\t\t\t\tmessages = Array.isArray(decoded) ? decoded : [decoded];\n\t\t\t}\n\t\t\tcatch (error) { return; }\n\t\t\tfor (const payload of messages) {\n\t\t\t\tif (!payload || payload.messageId !== messageId) continue;\n\t\t\t\tif (payload.data === \"ACK\" && payload.hasError === false) continue;\n\t\t\t\tif (payload.hasError) finish(new Error(typeof payload.data === \"string\" ? payload.data : JSON.stringify(payload.data || {})));\n\t\t\t\telse finish(null, payload.data);\n\t\t\t}\n\t\t});\n\t\tws.on(\"error\", (error) => finish(error));\n\t});\n}\n\tasync function readPartInventorySnapshotProofData(context, dayKey) {\n\t\tconst { MongoClient } = mongoRequire();\n\t\tconst client = new MongoClient(safeLocalMongoUrl());\n\tawait client.connect();\n\ttry {\n\t\tconst db = client.db();\n\t\tconst query = { section: \"Parts\" };\n\t\tif (dayKey) query.day_key = dayKey;\n\t\tconst partIds = uniqueStrings(context.part_ids || []);\n\t\tif (partIds.length) query.$or = [{ \"metadata.id_part\": { $in: partIds } }, { id_part: { $in: partIds } }];\n\t\tconst snapshots = await db.collection(\"inventory-daily-snapshots\").find(query).sort({ day_key: -1, item_name: 1, location: 1 }).limit(120).toArray();\n\t\tconst yardIds = uniqueStrings(snapshots.map(partSnapshotRowYardId));\n\t\tconst yards = yardIds.length ? await db.collection(\"yards\").find({ _id: { $in: yardIds } }).project({ _id: 1, name: 1 }).toArray() : [];\n\t\tconst yardById = new Map(yards.map((yard) => [String(yard._id), yard]));\n\t\treturn { snapshots, yards, yardById };\n\t} finally {\n\t\t\tawait client.close().catch(() => undefined);\n\t\t}\n\t}\n\tfunction findPartInventorySnapshotStaleRows(data) {\n\t\tconst yardById = data && data.yardById instanceof Map ? data.yardById : new Map();\n\t\treturn (Array.isArray(data && data.snapshots) ? data.snapshots : []).filter((snapshot) => {\n\t\t\tconst yardId = partSnapshotRowYardId(snapshot);\n\t\t\tconst yard = yardId ? yardById.get(String(yardId)) : null;\n\t\t\tconst expectedYardName = String(yard && yard.name || \"\").trim();\n\t\t\tconst beforeWarehouse = String(snapshot && (snapshot.warehouse_location || snapshot.location) || \"\").trim();\n\t\t\treturn !!expectedYardName && !!beforeWarehouse && normalizeWarehouseName(beforeWarehouse) !== normalizeWarehouseName(expectedYardName);\n\t\t});\n\t}\n\tfunction chooseSyntheticPartInventoryStaleWarehouse(expectedYardName, yards) {\n\t\tconst expected = normalizeWarehouseName(expectedYardName);\n\t\tconst alternate = (Array.isArray(yards) ? yards : []).map((yard) => String(yard && yard.name || \"\").trim()).find((name) => name && normalizeWarehouseName(name) !== expected);\n\t\tif (alternate) return alternate;\n\t\treturn expected === \"midland\" ? \"Kermit\" : \"Midland\";\n\t}\n\tasync function ensurePartInventorySnapshotStaleBaseline(context, dayKey, data) {\n\t\tif (findPartInventorySnapshotStaleRows(data).length) {\n\t\t\treturn { data, syntheticStaleBaseline: false };\n\t\t}\n\t\tconst candidate = (Array.isArray(data && data.snapshots) ? data.snapshots : []).find((snapshot) => {\n\t\t\tconst yardId = partSnapshotRowYardId(snapshot);\n\t\t\tconst yard = yardId ? data.yardById.get(String(yardId)) : null;\n\t\t\tconst expectedYardName = String(yard && yard.name || \"\").trim();\n\t\t\treturn snapshot && snapshot._id && expectedYardName;\n\t\t});\n\t\tif (!candidate) {\n\t\t\treturn { data, syntheticStaleBaseline: false, syntheticSkippedReason: \"No seeded Part snapshot row had both _id and metadata.id_yard lineage.\" };\n\t\t}\n\t\tconst yardId = partSnapshotRowYardId(candidate);\n\t\tconst yard = yardId ? data.yardById.get(String(yardId)) : null;\n\t\tconst expectedYardName = String(yard && yard.name || \"\").trim();\n\t\tconst originalWarehouse = String(candidate.warehouse_location || candidate.location || \"\").trim();\n\t\tconst staleWarehouse = chooseSyntheticPartInventoryStaleWarehouse(expectedYardName, data.yards);\n\t\tconst { MongoClient } = mongoRequire();\n\t\tconst client = new MongoClient(safeLocalMongoUrl());\n\t\tawait client.connect();\n\t\ttry {\n\t\t\tawait client.db().collection(\"inventory-daily-snapshots\").updateOne(\n\t\t\t\t{ _id: candidate._id },\n\t\t\t\t{\n\t\t\t\t\t$set: {\n\t\t\t\t\t\twarehouse_location: staleWarehouse,\n\t\t\t\t\t\tlocation: staleWarehouse,\n\t\t\t\t\t\t\"metadata.resolveio_qa_synthetic_stale_baseline\": true,\n\t\t\t\t\t\t\"metadata.resolveio_qa_original_warehouse_location\": originalWarehouse,\n\t\t\t\t\t\t\"metadata.resolveio_qa_expected_warehouse_location\": expectedYardName,\n\t\t\t\t\t\t\"metadata.resolveio_qa_synthetic_stale_at\": new Date().toISOString()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\t\t} finally {\n\t\t\tawait client.close().catch(() => undefined);\n\t\t}\n\t\tconst refreshedData = await readPartInventorySnapshotProofData(context, dayKey);\n\t\treturn {\n\t\t\tdata: refreshedData,\n\t\t\tsyntheticStaleBaseline: true,\n\t\t\tsyntheticBaseline: {\n\t\t\t\tcollection: \"inventory-daily-snapshots\",\n\t\t\t\t_id: String(candidate._id || \"\"),\n\t\t\t\tpartId: partSnapshotRowPartId(candidate),\n\t\t\t\titemName: String(candidate.item_name || candidate.name || \"\"),\n\t\t\t\tdayKey,\n\t\t\t\toriginalWarehouse,\n\t\t\t\tstaleWarehouse,\n\t\t\t\texpectedYardName\n\t\t\t}\n\t\t};\n\t}\n\tasync function renderPartInventorySnapshotProofScreenshot(page, payload, rowRoute, screenshotPath) {\n\tconst escapeHtml = (value) => String(value === undefined || value === null ? \"\" : value).replace(/[&<>\"']/g, (char) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", \"\"\": \"&quot;\", \"'\": \"&#39;\" }[char]));\n\tconst comparisons = (payload.comparisons || []).slice(0, 12).map((entry) => \"<tr><td>\" + escapeHtml(entry.partId || entry.itemName || \"part\") + \"</td><td>\" + escapeHtml(entry.beforeWarehouse || \"\") + \"</td><td>\" + escapeHtml(entry.expectedYardName || \"\") + \"</td><td>\" + escapeHtml(entry.afterWarehouse || \"\") + \"</td><td>\" + (entry.pass ? \"<span class=\"ok\">PASS</span>\" : \"<span class=\"bad\">FAIL</span>\") + \"</td></tr>\").join(\"\");\n\tconst html = \"<!doctype html><html><head><meta charset=\"utf-8\"><style>body{margin:0;background:#f5f7fb;color:#111827;font-family:Arial,Helvetica,sans-serif}.wrap{padding:42px 52px}.eyebrow{font-size:16px;text-transform:uppercase;color:#475569;font-weight:700}.title{font-size:34px;font-weight:800;margin:10px 0}.caption{font-size:20px;line-height:1.35;color:#1f2937;margin:0 0 22px}.grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin:22px 0}.card{background:white;border:2px solid #d7dde8;border-radius:8px;padding:18px}.label{font-size:14px;color:#64748b;font-weight:700;text-transform:uppercase}.value{font-size:28px;font-weight:800;margin-top:8px}.ok{color:#047857;font-weight:800}.bad{color:#b91c1c;font-weight:800}table{width:100%;border-collapse:collapse;background:white;border:2px solid #d7dde8;border-radius:8px;overflow:hidden;font-size:17px}th{background:#111827;color:white;text-align:left;padding:12px}td{border-top:1px solid #e5e7eb;padding:12px}.footer{margin-top:20px;font-size:16px;color:#475569}.path{font-family:Menlo,Consolas,monospace;font-size:14px;color:#334155}</style></head><body><main class=\"wrap\"><div class=\"eyebrow\">ResolveIO Support QA Business Proof</div><div class=\"title\">Part Inventory Snapshot Warehouse Attribution</div><p class=\"caption\">\" + escapeHtml(payload.caption || \"\") + \"</p><section class=\"grid\"><div class=\"card\"><div class=\"label\">Report Method</div><div class=\"value\">reportDailyInventorySnapshot</div></div><div class=\"card\"><div class=\"label\">Snapshot Date</div><div class=\"value\">\" + escapeHtml(payload.dayKey || \"\") + \"</div></div><div class=\"card\"><div class=\"label\">Stale Rows Corrected</div><div class=\"value \" + (payload.pass ? \"ok\" : \"bad\") + \"\">\" + Number(payload.correctedStaleRows || 0) + \"/\" + Number(payload.staleRows || 0) + \"</div></div></section><table><thead><tr><th>Part</th><th>Before Saved Warehouse</th><th>Expected Yard</th><th>Method Response Warehouse</th><th>Result</th></tr></thead><tbody>\" + comparisons + \"</tbody></table><div class=\"footer\">Before: saved historical Parts snapshot rows contained stale warehouse_location values. Action: called reportDailyInventorySnapshot(date, [\"Parts\"]) on the local QA server. After: the method response used the authoritative yard lineage for the affected part rows.</div><div class=\"footer path\">Route: \" + escapeHtml(rowRoute || targetRoute) + \" | Artifacts: qa-part-inventory-snapshot-proof.json, aiqa-business-assertion.json, qa-coverage-matrix.json</div></main></body></html>\";\n\tawait page.setViewport({ width: Math.max(viewportWidth, 1600), height: Math.max(viewportHeight, 900) }).catch(() => undefined);\n\tawait page.setContent(html, { waitUntil: \"domcontentloaded\", timeout: 30000 });\n\tawait page.screenshot({ path: screenshotPath, type: \"png\", fullPage: false });\n}\nasync function runPartInventorySnapshotMethodProof(page, routeSummary, auth) {\n\tif (!shouldRunPartInventorySnapshotProof()) return null;\n\tconst context = readSeedPartInventorySnapshotContext();\n\tconst row = activeMatrixRow();\n\tconst rowRoute = String(row.route || row.screen || \"/print\").trim() || \"/print\";\n\tconst rowWorkflow = String(row.workflow || row.action_under_test || \"Invoke reportDailyInventorySnapshot(date, [\"Parts\"]) and verify Part warehouse_location uses authoritative yard lineage.\").trim();\n\tconst proofPath = path.join(artifactDir, \"qa-part-inventory-snapshot-proof.json\");\n\tconst businessAssertionPath = path.join(artifactDir, \"aiqa-business-assertion.json\");\n\tconst beforeAfterScreenshotPath = path.join(artifactDir, \"support-business-proof-before-after.png\");\n\tconst requestedDayKeys = uniqueStrings(context.day_keys || []).map(normalizePartSnapshotDayKey).filter(Boolean);\n\tconst dayKey = requestedDayKeys[0] || normalizePartSnapshotDayKey(new Date(Date.now() - 86400000).toISOString());\n\tconst writeFailed = async (message, extra) => {\n\t\tconst payload = { status: \"failed\", targetRoute: rowRoute, dayKey, context, error: message, ...(extra || {}), updated_at: new Date().toISOString() };\n\t\twriteJson(proofPath, payload);\n\t\twriteJson(businessAssertionPath, { assertion: String(row.assertion || row.required_proof || \"Part inventory snapshot warehouse attribution proof\"), status: \"failed\", result: \"business_assertion_failed\", outcome: \"business_assertion_failed\", workflow: rowWorkflow, route: rowRoute, before: \"\", action: \"Attempted deterministic reportDailyInventorySnapshot websocket method proof.\", expected: \"Affected Part rows return the yard named by snapshot metadata.id_yard instead of stale saved warehouse_location.\", after: \"\", observed: message, dataProof: message, mongoDelta: {}, artifactPaths: [proofPath, matrixPath], metadata: { supportDiagnosisProof: false, proofType: \"part_inventory_snapshot_report_method\", generatedBy: \"resolveio_runner_part_inventory_snapshot_business_proof\", generatedAt: new Date().toISOString() } });\n\t\tconst matrix = readJson(matrixPath) || { status: \"started\", rows: [] };\n\t\tconst rows = matrixRows(matrix);\n\t\tconst activeIndex = Math.max(0, rows.indexOf(row));\n\t\tif (rows[activeIndex]) { rows[activeIndex].status = \"failed\"; rows[activeIndex].result = \"failed\"; rows[activeIndex].acceptance_blocked = true; rows[activeIndex].blocker = message; rows[activeIndex].artifact = proofPath; }\n\t\tmatrix.rows = rows;\n\t\tmatrix.status = \"failed\";\n\t\tmatrix.part_inventory_snapshot_proof = proofPath;\n\t\tmatrix.aiqa_business_assertion = businessAssertionPath;\n\t\tmatrix.updated_at = new Date().toISOString();\n\t\twriteJson(matrixPath, matrix);\n\t\treturn payload;\n\t\t};\n\t\ttry {\n\t\t\tlet data = await readPartInventorySnapshotProofData(context, dayKey);\n\t\t\tif (!data.snapshots.length) return await writeFailed(\"No seeded Part inventory daily snapshot rows were available for day_key \" + dayKey + \".\");\n\t\t\tconst baseline = await ensurePartInventorySnapshotStaleBaseline(context, dayKey, data);\n\t\t\tdata = baseline.data || data;\n\t\t\tconst methodResult = await callResolveIOMethodViaWs(auth, rowRoute, \"reportDailyInventorySnapshot\", [methodDateFromDayKey(dayKey), [\"Parts\"]]);\n\t\tconst responseRows = Array.isArray(methodResult && methodResult.rows) ? methodResult.rows : [];\n\t\tconst comparisons = data.snapshots.map((snapshot) => {\n\t\t\tconst partId = partSnapshotRowPartId(snapshot);\n\t\t\tconst yardId = partSnapshotRowYardId(snapshot);\n\t\t\tconst yard = yardId ? data.yardById.get(String(yardId)) : null;\n\t\t\tconst expectedYardName = String(yard && yard.name || \"\").trim();\n\t\t\tconst beforeWarehouse = String(snapshot.warehouse_location || snapshot.location || \"\").trim();\n\t\t\tconst responseCandidates = responseRows.filter((candidate) => !partId || partSnapshotRowPartId(candidate) === partId || String(candidate.item_name || candidate.name || \"\") === String(snapshot.item_name || snapshot.name || \"\"));\n\t\t\tconst afterRow = responseCandidates.find((candidate) => expectedYardName && normalizeWarehouseName(candidate.warehouse_location || candidate.location) === normalizeWarehouseName(expectedYardName)) || responseCandidates[0] || null;\n\t\t\tconst afterWarehouse = String(afterRow && (afterRow.warehouse_location || afterRow.location) || \"\").trim();\n\t\t\tconst staleBefore = !!expectedYardName && !!beforeWarehouse && normalizeWarehouseName(beforeWarehouse) !== normalizeWarehouseName(expectedYardName);\n\t\t\treturn { partId, itemName: String(snapshot.item_name || snapshot.name || \"\"), dayKey: String(snapshot.day_key || \"\"), beforeWarehouse, expectedYardName, afterWarehouse, staleBefore, pass: staleBefore && !!afterWarehouse && normalizeWarehouseName(afterWarehouse) === normalizeWarehouseName(expectedYardName) };\n\t\t}).filter((entry) => entry.expectedYardName);\n\t\tconst staleRows = comparisons.filter((entry) => entry.staleBefore);\n\t\tconst passedRows = staleRows.filter((entry) => entry.pass);\n\t\t\tconst pass = staleRows.length > 0 && passedRows.length === staleRows.length;\n\t\t\tconst caption = pass\n\t\t\t\t? \"Part inventory snapshot proof passed: \" + passedRows.length + \" stale saved row(s) returned the authoritative yard warehouse in reportDailyInventorySnapshot\" + (baseline.syntheticStaleBaseline ? \" using a local-only stale baseline copied from seeded live data.\" : \".\")\n\t\t\t\t: \"Part inventory snapshot proof failed: staleRows=\" + staleRows.length + \", corrected=\" + passedRows.length + (baseline.syntheticSkippedReason ? \"; synthetic baseline skipped: \" + baseline.syntheticSkippedReason : \"\") + \".\";\n\t\t\tconst payload = { status: pass ? \"pass\" : \"failed\", targetRoute: rowRoute, dayKey, methodName: \"reportDailyInventorySnapshot\", context, snapshotCount: data.snapshots.length, responseRowCount: responseRows.length, staleRows: staleRows.length, correctedStaleRows: passedRows.length, syntheticStaleBaseline: !!baseline.syntheticStaleBaseline, syntheticBaseline: baseline.syntheticBaseline || null, comparisons, caption, screenshot: beforeAfterScreenshotPath, page: routeSummary, updated_at: new Date().toISOString() };\n\t\tawait renderPartInventorySnapshotProofScreenshot(page, payload, rowRoute, beforeAfterScreenshotPath).catch(() => undefined);\n\t\twriteJson(proofPath, payload);\n\t\tconst businessAssertion = {\n\t\t\tassertion: String(row.assertion || row.required_proof || \"Affected Part warehouse_location maps to authoritative yard lineage in reportDailyInventorySnapshot response.\").trim(),\n\t\t\tstatus: pass ? \"pass\" : \"failed\",\n\t\t\tresult: pass ? \"business_assertion_passed\" : \"business_assertion_failed\",\n\t\t\toutcome: pass ? \"business_assertion_passed\" : \"business_assertion_failed\",\n\t\t\tworkflow: rowWorkflow,\n\t\t\troute: rowRoute,\n\t\t\t\tbefore: (baseline.syntheticStaleBaseline ? \"Local QA created a stale saved Parts snapshot baseline from copied seeded data for day_key \" + dayKey + \": \" + String((baseline.syntheticBaseline && (baseline.syntheticBaseline.itemName || baseline.syntheticBaseline.partId)) || \"part\") + \"=\" + String((baseline.syntheticBaseline && baseline.syntheticBaseline.staleWarehouse) || \"\") + \" while expected yard lineage was \" + String((baseline.syntheticBaseline && baseline.syntheticBaseline.expectedYardName) || \"\") + \". \" : \"\") + \"Saved non-today Parts snapshot rows for day_key \" + dayKey + \" included \" + staleRows.length + \" stale warehouse_location value(s): \" + staleRows.map((entry) => (entry.itemName || entry.partId) + \"=\" + entry.beforeWarehouse).join(\"; \"),\n\t\t\taction: \"Called reportDailyInventorySnapshot(\" + dayKey + \", [\"Parts\"]) through the local QA websocket method path.\",\n\t\t\texpected: \"Every affected stale Part row returns the yard named by metadata.id_yard in warehouse_location.\",\n\t\t\tafter: \"Method response corrected \" + passedRows.length + \" of \" + staleRows.length + \" stale row(s): \" + passedRows.map((entry) => (entry.itemName || entry.partId) + \"=\" + entry.afterWarehouse).join(\"; \"),\n\t\t\tobserved: caption,\n\t\t\tdataProof: comparisons.map((entry) => (entry.itemName || entry.partId || \"part\") + \": before=\" + entry.beforeWarehouse + \", expected=\" + entry.expectedYardName + \", after=\" + entry.afterWarehouse + \", pass=\" + entry.pass).join(\"\n\"),\n\t\t\t\tmongoDelta: { collection: \"inventory-daily-snapshots\", day_key: dayKey, staleRows: staleRows.length, correctedStaleRows: passedRows.length, responseRowCount: responseRows.length, syntheticStaleBaseline: !!baseline.syntheticStaleBaseline, syntheticBaseline: baseline.syntheticBaseline || null },\n\t\t\t\tartifactPaths: [proofPath, beforeAfterScreenshotPath, matrixPath],\n\t\t\t\tmetadata: { supportDiagnosisProof: pass, proofType: \"part_inventory_snapshot_report_method\", methodPath: \"server/src/methods/report.ts reportDailyInventorySnapshot\", generatedBy: \"resolveio_runner_part_inventory_snapshot_business_proof\", syntheticStaleBaseline: !!baseline.syntheticStaleBaseline, generatedAt: new Date().toISOString() }\n\t\t};\n\t\twriteJson(businessAssertionPath, businessAssertion);\n\t\tconst matrix = readJson(matrixPath) || { status: \"started\", rows: [] };\n\t\tconst rows = matrixRows(matrix);\n\t\tconst rowIndex = rows.findIndex((candidate) => String(candidate && candidate.workflow || \"\") === rowWorkflow) >= 0 ? rows.findIndex((candidate) => String(candidate && candidate.workflow || \"\") === rowWorkflow) : rows.findIndex((candidate) => !/^(pass|passed)$/i.test(String(candidate && candidate.status || \"\")));\n\t\tconst activeIndex = rowIndex >= 0 ? rowIndex : 0;\n\t\tif (rows[activeIndex]) {\n\t\t\trows[activeIndex].status = pass ? \"pass\" : \"failed\";\n\t\t\trows[activeIndex].result = pass ? \"pass\" : \"failed\";\n\t\t\trows[activeIndex].acceptance_blocked = !pass;\n\t\t\trows[activeIndex].screenshot = beforeAfterScreenshotPath;\n\t\t\trows[activeIndex].screenshots = [beforeAfterScreenshotPath, passScreenshotPath].filter(Boolean);\n\t\t\trows[activeIndex].caption = caption;\n\t\t\trows[activeIndex].artifact = proofPath;\n\t\t\trows[activeIndex].artifacts = [{ path: proofPath, caption: \"Part inventory snapshot method response proof.\" }, { screenshot: beforeAfterScreenshotPath, caption }, { path: businessAssertionPath, caption: \"AIQaBusinessAssertion for support diagnosis proof plan.\" }];\n\t\t\trows[activeIndex].persisted_assertion = businessAssertion.dataProof;\n\t\t\tif (!pass) rows[activeIndex].blocker = caption;\n\t\t}\n\t\tmatrix.rows = rows;\n\t\tmatrix.status = pass ? \"pass\" : \"failed\";\n\t\tmatrix.part_inventory_snapshot_proof = proofPath;\n\t\tmatrix.aiqa_business_assertion = businessAssertionPath;\n\t\tmatrix.updated_at = new Date().toISOString();\n\t\twriteJson(matrixPath, matrix);\n\t\treturn payload;\n\t} catch (error) {\n\t\treturn await writeFailed(error && (error.stack || error.message) || String(error));\n\t}\n}\n"], ["\nfunction activeMatrixRow() {\n\tconst matrix = readJson(matrixPath) || {};\n\tconst rows = matrixRows(matrix);\n\treturn rows.find((candidate) => !/^(pass|passed)$/i.test(String(candidate && candidate.status || \"\"))) || rows[0] || {};\n}\nfunction readSeedPartInventorySnapshotContext() {\n\tconst seed = readJson(path.join(artifactDir, \"qa-live-data-seed-result.json\")) || {};\n\tconst selected = seed && seed.selected || {};\n\tconst context = selected.part_inventory_snapshot_context || selected.partInventorySnapshotContext || null;\n\tif (!context || typeof context !== \"object\") return null;\n\tconst partIds = uniqueStrings(context.part_ids || context.partIds || []);\n\tconst dayKeys = uniqueStrings(context.day_keys || context.dayKeys || []);\n\tif (!partIds.length && !dayKeys.length && !Number(context.snapshot_count || context.snapshotCount || 0)) return null;\n\treturn { ...context, part_ids: partIds, day_keys: dayKeys };\n}\nfunction shouldRunPartInventorySnapshotProof() {\n\tconst context = readSeedPartInventorySnapshotContext();\n\tif (!context) return false;\n\tconst text = [activeRowText(), targetRoute].join(\"\\n\");\n\treturn /\\b(reportDailyInventorySnapshot|inventory-daily-snapshots|daily\\s+inventory\\s+snapshot|part\\s+inventory\\s+snapshot|warehouse_location|Parts)\\b/i.test(text);\n}\nfunction normalizePartSnapshotDayKey(value) {\n\tconst text = String(value || \"\").trim();\n\tconst match = /\\d{4}-\\d{2}-\\d{2}/.exec(text);\n\tif (match) return match[0];\n\tconst date = new Date(text);\n\treturn Number.isFinite(date.getTime()) ? date.toISOString().slice(0, 10) : \"\";\n}\nfunction partSnapshotRowPartId(row) {\n\tconst metadata = row && typeof row.metadata === \"object\" ? row.metadata : {};\n\treturn String(metadata.id_part || row.id_part || row.id_item || row.id_source || \"\").trim();\n}\nfunction partSnapshotRowYardId(row) {\n\tconst metadata = row && typeof row.metadata === \"object\" ? row.metadata : {};\n\tconst explicit = String(metadata.id_yard || row.id_yard || row.id_warehouse_location || \"\").trim();\n\tif (explicit) return explicit;\n\tconst source = String(row && row.id_source || \"\");\n\tconst sourceMatch = /^yard:([A-Za-z0-9_-]+)/i.exec(source);\n\treturn sourceMatch ? sourceMatch[1] : \"\";\n}\nfunction normalizeWarehouseName(value) {\n\treturn String(value || \"\").trim().toLowerCase().replace(/\\s+/g, \" \");\n}\nfunction methodDateFromDayKey(dayKey) {\n\treturn new Date(dayKey + \"T12:00:00.000Z\");\n}\nfunction resolveWsUrl() {\n\tif (/^https:/i.test(serverUrl)) return serverUrl.replace(/^https:/i, \"wss:\");\n\tif (/^http:/i.test(serverUrl)) return serverUrl.replace(/^http:/i, \"ws:\");\n\treturn serverUrl;\n}\nfunction callResolveIOMethodViaWs(auth, route, methodName, args) {\n\treturn new Promise((resolve, reject) => {\n\t\tconst token = auth && auth.accessToken;\n\t\tif (!token) {\n\t\t\treject(new Error(\"Missing access token for websocket method proof.\"));\n\t\t\treturn;\n\t\t}\n\t\tconst WebSocket = requireQaModule(\"ws\");\n\t\tconst { unpack } = requireQaModule(\"msgpackr\");\n\t\tconst ws = new WebSocket(resolveWsUrl(), [token], { origin: clientUrl });\n\t\tconst messageId = Date.now() + Math.floor(Math.random() * 100000);\n\t\tconst timeout = setTimeout(() => {\n\t\t\ttry { ws.close(); } catch (error) {}\n\t\t\treject(new Error(\"Timed out waiting for \" + methodName + \" websocket response.\"));\n\t\t}, Number(process.env.RESOLVEIO_SUPPORT_QA_METHOD_PROOF_TIMEOUT_MS || process.env.RESOLVEIO_RUNNER_QA_METHOD_PROOF_TIMEOUT_MS || 30000));\n\t\tlet settled = false;\n\t\tconst finish = (error, value) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timeout);\n\t\t\ttry { ws.close(); } catch (closeError) {}\n\t\t\tif (error) reject(error);\n\t\t\telse resolve(value);\n\t\t};\n\t\tws.on(\"open\", () => {\n\t\t\tconst payload = [[route || \"/\", new Date().toISOString(), messageId, \"method\", methodName, ...(args || [])]];\n\t\t\tws.send(JSON.stringify(payload));\n\t\t});\n\t\tws.on(\"message\", (raw) => {\n\t\t\tlet messages = [];\n\t\t\tconst text = Buffer.isBuffer(raw) ? raw.toString(\"utf8\") : String(raw || \"\");\n\t\t\tif (text === \"ping\") {\n\t\t\t\ttry { ws.send(\"pong\"); } catch (error) {}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst decoded = Buffer.isBuffer(raw) ? unpack(raw) : JSON.parse(text);\n\t\t\t\tmessages = Array.isArray(decoded) ? decoded : [decoded];\n\t\t\t}\n\t\t\tcatch (error) { return; }\n\t\t\tfor (const payload of messages) {\n\t\t\t\tif (!payload || payload.messageId !== messageId) continue;\n\t\t\t\tif (payload.data === \"ACK\" && payload.hasError === false) continue;\n\t\t\t\tif (payload.hasError) finish(new Error(typeof payload.data === \"string\" ? payload.data : JSON.stringify(payload.data || {})));\n\t\t\t\telse finish(null, payload.data);\n\t\t\t}\n\t\t});\n\t\tws.on(\"error\", (error) => finish(error));\n\t});\n}\n\tasync function readPartInventorySnapshotProofData(context, dayKey) {\n\t\tconst { MongoClient } = mongoRequire();\n\t\tconst client = new MongoClient(safeLocalMongoUrl());\n\tawait client.connect();\n\ttry {\n\t\tconst db = client.db();\n\t\tconst query = { section: \"Parts\" };\n\t\tif (dayKey) query.day_key = dayKey;\n\t\tconst partIds = uniqueStrings(context.part_ids || []);\n\t\tif (partIds.length) query.$or = [{ \"metadata.id_part\": { $in: partIds } }, { id_part: { $in: partIds } }];\n\t\tconst snapshots = await db.collection(\"inventory-daily-snapshots\").find(query).sort({ day_key: -1, item_name: 1, location: 1 }).limit(120).toArray();\n\t\tconst yardIds = uniqueStrings(snapshots.map(partSnapshotRowYardId));\n\t\tconst yards = yardIds.length ? await db.collection(\"yards\").find({ _id: { $in: yardIds } }).project({ _id: 1, name: 1 }).toArray() : [];\n\t\tconst yardById = new Map(yards.map((yard) => [String(yard._id), yard]));\n\t\treturn { snapshots, yards, yardById };\n\t} finally {\n\t\t\tawait client.close().catch(() => undefined);\n\t\t}\n\t}\n\tfunction findPartInventorySnapshotStaleRows(data) {\n\t\tconst yardById = data && data.yardById instanceof Map ? data.yardById : new Map();\n\t\treturn (Array.isArray(data && data.snapshots) ? data.snapshots : []).filter((snapshot) => {\n\t\t\tconst yardId = partSnapshotRowYardId(snapshot);\n\t\t\tconst yard = yardId ? yardById.get(String(yardId)) : null;\n\t\t\tconst expectedYardName = String(yard && yard.name || \"\").trim();\n\t\t\tconst beforeWarehouse = String(snapshot && (snapshot.warehouse_location || snapshot.location) || \"\").trim();\n\t\t\treturn !!expectedYardName && !!beforeWarehouse && normalizeWarehouseName(beforeWarehouse) !== normalizeWarehouseName(expectedYardName);\n\t\t});\n\t}\n\tfunction chooseSyntheticPartInventoryStaleWarehouse(expectedYardName, yards) {\n\t\tconst expected = normalizeWarehouseName(expectedYardName);\n\t\tconst alternate = (Array.isArray(yards) ? yards : []).map((yard) => String(yard && yard.name || \"\").trim()).find((name) => name && normalizeWarehouseName(name) !== expected);\n\t\tif (alternate) return alternate;\n\t\treturn expected === \"midland\" ? \"Kermit\" : \"Midland\";\n\t}\n\tasync function ensurePartInventorySnapshotStaleBaseline(context, dayKey, data) {\n\t\tif (findPartInventorySnapshotStaleRows(data).length) {\n\t\t\treturn { data, syntheticStaleBaseline: false };\n\t\t}\n\t\tconst candidate = (Array.isArray(data && data.snapshots) ? data.snapshots : []).find((snapshot) => {\n\t\t\tconst yardId = partSnapshotRowYardId(snapshot);\n\t\t\tconst yard = yardId ? data.yardById.get(String(yardId)) : null;\n\t\t\tconst expectedYardName = String(yard && yard.name || \"\").trim();\n\t\t\treturn snapshot && snapshot._id && expectedYardName;\n\t\t});\n\t\tif (!candidate) {\n\t\t\treturn { data, syntheticStaleBaseline: false, syntheticSkippedReason: \"No seeded Part snapshot row had both _id and metadata.id_yard lineage.\" };\n\t\t}\n\t\tconst yardId = partSnapshotRowYardId(candidate);\n\t\tconst yard = yardId ? data.yardById.get(String(yardId)) : null;\n\t\tconst expectedYardName = String(yard && yard.name || \"\").trim();\n\t\tconst originalWarehouse = String(candidate.warehouse_location || candidate.location || \"\").trim();\n\t\tconst staleWarehouse = chooseSyntheticPartInventoryStaleWarehouse(expectedYardName, data.yards);\n\t\tconst { MongoClient } = mongoRequire();\n\t\tconst client = new MongoClient(safeLocalMongoUrl());\n\t\tawait client.connect();\n\t\ttry {\n\t\t\tawait client.db().collection(\"inventory-daily-snapshots\").updateOne(\n\t\t\t\t{ _id: candidate._id },\n\t\t\t\t{\n\t\t\t\t\t$set: {\n\t\t\t\t\t\twarehouse_location: staleWarehouse,\n\t\t\t\t\t\tlocation: staleWarehouse,\n\t\t\t\t\t\t\"metadata.resolveio_qa_synthetic_stale_baseline\": true,\n\t\t\t\t\t\t\"metadata.resolveio_qa_original_warehouse_location\": originalWarehouse,\n\t\t\t\t\t\t\"metadata.resolveio_qa_expected_warehouse_location\": expectedYardName,\n\t\t\t\t\t\t\"metadata.resolveio_qa_synthetic_stale_at\": new Date().toISOString()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\t\t} finally {\n\t\t\tawait client.close().catch(() => undefined);\n\t\t}\n\t\tconst refreshedData = await readPartInventorySnapshotProofData(context, dayKey);\n\t\treturn {\n\t\t\tdata: refreshedData,\n\t\t\tsyntheticStaleBaseline: true,\n\t\t\tsyntheticBaseline: {\n\t\t\t\tcollection: \"inventory-daily-snapshots\",\n\t\t\t\t_id: String(candidate._id || \"\"),\n\t\t\t\tpartId: partSnapshotRowPartId(candidate),\n\t\t\t\titemName: String(candidate.item_name || candidate.name || \"\"),\n\t\t\t\tdayKey,\n\t\t\t\toriginalWarehouse,\n\t\t\t\tstaleWarehouse,\n\t\t\t\texpectedYardName\n\t\t\t}\n\t\t};\n\t}\n\tasync function renderPartInventorySnapshotProofScreenshot(page, payload, rowRoute, screenshotPath) {\n\tconst escapeHtml = (value) => String(value === undefined || value === null ? \"\" : value).replace(/[&<>\"']/g, (char) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", \"\\\"\": \"&quot;\", \"'\": \"&#39;\" }[char]));\n\tconst comparisons = (payload.comparisons || []).slice(0, 12).map((entry) => \"<tr><td>\" + escapeHtml(entry.partId || entry.itemName || \"part\") + \"</td><td>\" + escapeHtml(entry.beforeWarehouse || \"\") + \"</td><td>\" + escapeHtml(entry.expectedYardName || \"\") + \"</td><td>\" + escapeHtml(entry.afterWarehouse || \"\") + \"</td><td>\" + (entry.pass ? \"<span class=\\\"ok\\\">PASS</span>\" : \"<span class=\\\"bad\\\">FAIL</span>\") + \"</td></tr>\").join(\"\");\n\tconst html = \"<!doctype html><html><head><meta charset=\\\"utf-8\\\"><style>body{margin:0;background:#f5f7fb;color:#111827;font-family:Arial,Helvetica,sans-serif}.wrap{padding:42px 52px}.eyebrow{font-size:16px;text-transform:uppercase;color:#475569;font-weight:700}.title{font-size:34px;font-weight:800;margin:10px 0}.caption{font-size:20px;line-height:1.35;color:#1f2937;margin:0 0 22px}.grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin:22px 0}.card{background:white;border:2px solid #d7dde8;border-radius:8px;padding:18px}.label{font-size:14px;color:#64748b;font-weight:700;text-transform:uppercase}.value{font-size:28px;font-weight:800;margin-top:8px}.ok{color:#047857;font-weight:800}.bad{color:#b91c1c;font-weight:800}table{width:100%;border-collapse:collapse;background:white;border:2px solid #d7dde8;border-radius:8px;overflow:hidden;font-size:17px}th{background:#111827;color:white;text-align:left;padding:12px}td{border-top:1px solid #e5e7eb;padding:12px}.footer{margin-top:20px;font-size:16px;color:#475569}.path{font-family:Menlo,Consolas,monospace;font-size:14px;color:#334155}</style></head><body><main class=\\\"wrap\\\"><div class=\\\"eyebrow\\\">ResolveIO Support QA Business Proof</div><div class=\\\"title\\\">Part Inventory Snapshot Warehouse Attribution</div><p class=\\\"caption\\\">\" + escapeHtml(payload.caption || \"\") + \"</p><section class=\\\"grid\\\"><div class=\\\"card\\\"><div class=\\\"label\\\">Report Method</div><div class=\\\"value\\\">reportDailyInventorySnapshot</div></div><div class=\\\"card\\\"><div class=\\\"label\\\">Snapshot Date</div><div class=\\\"value\\\">\" + escapeHtml(payload.dayKey || \"\") + \"</div></div><div class=\\\"card\\\"><div class=\\\"label\\\">Stale Rows Corrected</div><div class=\\\"value \" + (payload.pass ? \"ok\" : \"bad\") + \"\\\">\" + Number(payload.correctedStaleRows || 0) + \"/\" + Number(payload.staleRows || 0) + \"</div></div></section><table><thead><tr><th>Part</th><th>Before Saved Warehouse</th><th>Expected Yard</th><th>Method Response Warehouse</th><th>Result</th></tr></thead><tbody>\" + comparisons + \"</tbody></table><div class=\\\"footer\\\">Before: saved historical Parts snapshot rows contained stale warehouse_location values. Action: called reportDailyInventorySnapshot(date, [\\\"Parts\\\"]) on the local QA server. After: the method response used the authoritative yard lineage for the affected part rows.</div><div class=\\\"footer path\\\">Route: \" + escapeHtml(rowRoute || targetRoute) + \" | Artifacts: qa-part-inventory-snapshot-proof.json, aiqa-business-assertion.json, qa-coverage-matrix.json</div></main></body></html>\";\n\tawait page.setViewport({ width: Math.max(viewportWidth, 1600), height: Math.max(viewportHeight, 900) }).catch(() => undefined);\n\tawait page.setContent(html, { waitUntil: \"domcontentloaded\", timeout: 30000 });\n\tawait page.screenshot({ path: screenshotPath, type: \"png\", fullPage: false });\n}\nasync function runPartInventorySnapshotMethodProof(page, routeSummary, auth) {\n\tif (!shouldRunPartInventorySnapshotProof()) return null;\n\tconst context = readSeedPartInventorySnapshotContext();\n\tconst row = activeMatrixRow();\n\tconst rowRoute = String(row.route || row.screen || \"/print\").trim() || \"/print\";\n\tconst rowWorkflow = String(row.workflow || row.action_under_test || \"Invoke reportDailyInventorySnapshot(date, [\\\"Parts\\\"]) and verify Part warehouse_location uses authoritative yard lineage.\").trim();\n\tconst proofPath = path.join(artifactDir, \"qa-part-inventory-snapshot-proof.json\");\n\tconst businessAssertionPath = path.join(artifactDir, \"aiqa-business-assertion.json\");\n\tconst beforeAfterScreenshotPath = path.join(artifactDir, \"support-business-proof-before-after.png\");\n\tconst requestedDayKeys = uniqueStrings(context.day_keys || []).map(normalizePartSnapshotDayKey).filter(Boolean);\n\tconst dayKey = requestedDayKeys[0] || normalizePartSnapshotDayKey(new Date(Date.now() - 86400000).toISOString());\n\tconst writeFailed = async (message, extra) => {\n\t\tconst payload = { status: \"failed\", targetRoute: rowRoute, dayKey, context, error: message, ...(extra || {}), updated_at: new Date().toISOString() };\n\t\twriteJson(proofPath, payload);\n\t\twriteJson(businessAssertionPath, { assertion: String(row.assertion || row.required_proof || \"Part inventory snapshot warehouse attribution proof\"), status: \"failed\", result: \"business_assertion_failed\", outcome: \"business_assertion_failed\", workflow: rowWorkflow, route: rowRoute, before: \"\", action: \"Attempted deterministic reportDailyInventorySnapshot websocket method proof.\", expected: \"Affected Part rows return the yard named by snapshot metadata.id_yard instead of stale saved warehouse_location.\", after: \"\", observed: message, dataProof: message, mongoDelta: {}, artifactPaths: [proofPath, matrixPath], metadata: { supportDiagnosisProof: false, proofType: \"part_inventory_snapshot_report_method\", generatedBy: \"resolveio_runner_part_inventory_snapshot_business_proof\", generatedAt: new Date().toISOString() } });\n\t\tconst matrix = readJson(matrixPath) || { status: \"started\", rows: [] };\n\t\tconst rows = matrixRows(matrix);\n\t\tconst activeIndex = Math.max(0, rows.indexOf(row));\n\t\tif (rows[activeIndex]) { rows[activeIndex].status = \"failed\"; rows[activeIndex].result = \"failed\"; rows[activeIndex].acceptance_blocked = true; rows[activeIndex].blocker = message; rows[activeIndex].artifact = proofPath; }\n\t\tmatrix.rows = rows;\n\t\tmatrix.status = \"failed\";\n\t\tmatrix.part_inventory_snapshot_proof = proofPath;\n\t\tmatrix.aiqa_business_assertion = businessAssertionPath;\n\t\tmatrix.updated_at = new Date().toISOString();\n\t\twriteJson(matrixPath, matrix);\n\t\treturn payload;\n\t\t};\n\t\ttry {\n\t\t\tlet data = await readPartInventorySnapshotProofData(context, dayKey);\n\t\t\tif (!data.snapshots.length) return await writeFailed(\"No seeded Part inventory daily snapshot rows were available for day_key \" + dayKey + \".\");\n\t\t\tconst baseline = await ensurePartInventorySnapshotStaleBaseline(context, dayKey, data);\n\t\t\tdata = baseline.data || data;\n\t\t\tconst methodResult = await callResolveIOMethodViaWs(auth, rowRoute, \"reportDailyInventorySnapshot\", [methodDateFromDayKey(dayKey), [\"Parts\"]]);\n\t\tconst responseRows = Array.isArray(methodResult && methodResult.rows) ? methodResult.rows : [];\n\t\tconst comparisons = data.snapshots.map((snapshot) => {\n\t\t\tconst partId = partSnapshotRowPartId(snapshot);\n\t\t\tconst yardId = partSnapshotRowYardId(snapshot);\n\t\t\tconst yard = yardId ? data.yardById.get(String(yardId)) : null;\n\t\t\tconst expectedYardName = String(yard && yard.name || \"\").trim();\n\t\t\tconst beforeWarehouse = String(snapshot.warehouse_location || snapshot.location || \"\").trim();\n\t\t\tconst responseCandidates = responseRows.filter((candidate) => !partId || partSnapshotRowPartId(candidate) === partId || String(candidate.item_name || candidate.name || \"\") === String(snapshot.item_name || snapshot.name || \"\"));\n\t\t\tconst afterRow = responseCandidates.find((candidate) => expectedYardName && normalizeWarehouseName(candidate.warehouse_location || candidate.location) === normalizeWarehouseName(expectedYardName)) || responseCandidates[0] || null;\n\t\t\tconst afterWarehouse = String(afterRow && (afterRow.warehouse_location || afterRow.location) || \"\").trim();\n\t\t\tconst staleBefore = !!expectedYardName && !!beforeWarehouse && normalizeWarehouseName(beforeWarehouse) !== normalizeWarehouseName(expectedYardName);\n\t\t\treturn { partId, itemName: String(snapshot.item_name || snapshot.name || \"\"), dayKey: String(snapshot.day_key || \"\"), beforeWarehouse, expectedYardName, afterWarehouse, staleBefore, pass: staleBefore && !!afterWarehouse && normalizeWarehouseName(afterWarehouse) === normalizeWarehouseName(expectedYardName) };\n\t\t}).filter((entry) => entry.expectedYardName);\n\t\tconst staleRows = comparisons.filter((entry) => entry.staleBefore);\n\t\tconst passedRows = staleRows.filter((entry) => entry.pass);\n\t\t\tconst pass = staleRows.length > 0 && passedRows.length === staleRows.length;\n\t\t\tconst caption = pass\n\t\t\t\t? \"Part inventory snapshot proof passed: \" + passedRows.length + \" stale saved row(s) returned the authoritative yard warehouse in reportDailyInventorySnapshot\" + (baseline.syntheticStaleBaseline ? \" using a local-only stale baseline copied from seeded live data.\" : \".\")\n\t\t\t\t: \"Part inventory snapshot proof failed: staleRows=\" + staleRows.length + \", corrected=\" + passedRows.length + (baseline.syntheticSkippedReason ? \"; synthetic baseline skipped: \" + baseline.syntheticSkippedReason : \"\") + \".\";\n\t\t\tconst payload = { status: pass ? \"pass\" : \"failed\", targetRoute: rowRoute, dayKey, methodName: \"reportDailyInventorySnapshot\", context, snapshotCount: data.snapshots.length, responseRowCount: responseRows.length, staleRows: staleRows.length, correctedStaleRows: passedRows.length, syntheticStaleBaseline: !!baseline.syntheticStaleBaseline, syntheticBaseline: baseline.syntheticBaseline || null, comparisons, caption, screenshot: beforeAfterScreenshotPath, page: routeSummary, updated_at: new Date().toISOString() };\n\t\tawait renderPartInventorySnapshotProofScreenshot(page, payload, rowRoute, beforeAfterScreenshotPath).catch(() => undefined);\n\t\twriteJson(proofPath, payload);\n\t\tconst businessAssertion = {\n\t\t\tassertion: String(row.assertion || row.required_proof || \"Affected Part warehouse_location maps to authoritative yard lineage in reportDailyInventorySnapshot response.\").trim(),\n\t\t\tstatus: pass ? \"pass\" : \"failed\",\n\t\t\tresult: pass ? \"business_assertion_passed\" : \"business_assertion_failed\",\n\t\t\toutcome: pass ? \"business_assertion_passed\" : \"business_assertion_failed\",\n\t\t\tworkflow: rowWorkflow,\n\t\t\troute: rowRoute,\n\t\t\t\tbefore: (baseline.syntheticStaleBaseline ? \"Local QA created a stale saved Parts snapshot baseline from copied seeded data for day_key \" + dayKey + \": \" + String((baseline.syntheticBaseline && (baseline.syntheticBaseline.itemName || baseline.syntheticBaseline.partId)) || \"part\") + \"=\" + String((baseline.syntheticBaseline && baseline.syntheticBaseline.staleWarehouse) || \"\") + \" while expected yard lineage was \" + String((baseline.syntheticBaseline && baseline.syntheticBaseline.expectedYardName) || \"\") + \". \" : \"\") + \"Saved non-today Parts snapshot rows for day_key \" + dayKey + \" included \" + staleRows.length + \" stale warehouse_location value(s): \" + staleRows.map((entry) => (entry.itemName || entry.partId) + \"=\" + entry.beforeWarehouse).join(\"; \"),\n\t\t\taction: \"Called reportDailyInventorySnapshot(\" + dayKey + \", [\\\"Parts\\\"]) through the local QA websocket method path.\",\n\t\t\texpected: \"Every affected stale Part row returns the yard named by metadata.id_yard in warehouse_location.\",\n\t\t\tafter: \"Method response corrected \" + passedRows.length + \" of \" + staleRows.length + \" stale row(s): \" + passedRows.map((entry) => (entry.itemName || entry.partId) + \"=\" + entry.afterWarehouse).join(\"; \"),\n\t\t\tobserved: caption,\n\t\t\tdataProof: comparisons.map((entry) => (entry.itemName || entry.partId || \"part\") + \": before=\" + entry.beforeWarehouse + \", expected=\" + entry.expectedYardName + \", after=\" + entry.afterWarehouse + \", pass=\" + entry.pass).join(\"\\n\"),\n\t\t\t\tmongoDelta: { collection: \"inventory-daily-snapshots\", day_key: dayKey, staleRows: staleRows.length, correctedStaleRows: passedRows.length, responseRowCount: responseRows.length, syntheticStaleBaseline: !!baseline.syntheticStaleBaseline, syntheticBaseline: baseline.syntheticBaseline || null },\n\t\t\t\tartifactPaths: [proofPath, beforeAfterScreenshotPath, matrixPath],\n\t\t\t\tmetadata: { supportDiagnosisProof: pass, proofType: \"part_inventory_snapshot_report_method\", methodPath: \"server/src/methods/report.ts reportDailyInventorySnapshot\", generatedBy: \"resolveio_runner_part_inventory_snapshot_business_proof\", syntheticStaleBaseline: !!baseline.syntheticStaleBaseline, generatedAt: new Date().toISOString() }\n\t\t};\n\t\twriteJson(businessAssertionPath, businessAssertion);\n\t\tconst matrix = readJson(matrixPath) || { status: \"started\", rows: [] };\n\t\tconst rows = matrixRows(matrix);\n\t\tconst rowIndex = rows.findIndex((candidate) => String(candidate && candidate.workflow || \"\") === rowWorkflow) >= 0 ? rows.findIndex((candidate) => String(candidate && candidate.workflow || \"\") === rowWorkflow) : rows.findIndex((candidate) => !/^(pass|passed)$/i.test(String(candidate && candidate.status || \"\")));\n\t\tconst activeIndex = rowIndex >= 0 ? rowIndex : 0;\n\t\tif (rows[activeIndex]) {\n\t\t\trows[activeIndex].status = pass ? \"pass\" : \"failed\";\n\t\t\trows[activeIndex].result = pass ? \"pass\" : \"failed\";\n\t\t\trows[activeIndex].acceptance_blocked = !pass;\n\t\t\trows[activeIndex].screenshot = beforeAfterScreenshotPath;\n\t\t\trows[activeIndex].screenshots = [beforeAfterScreenshotPath, passScreenshotPath].filter(Boolean);\n\t\t\trows[activeIndex].caption = caption;\n\t\t\trows[activeIndex].artifact = proofPath;\n\t\t\trows[activeIndex].artifacts = [{ path: proofPath, caption: \"Part inventory snapshot method response proof.\" }, { screenshot: beforeAfterScreenshotPath, caption }, { path: businessAssertionPath, caption: \"AIQaBusinessAssertion for support diagnosis proof plan.\" }];\n\t\t\trows[activeIndex].persisted_assertion = businessAssertion.dataProof;\n\t\t\tif (!pass) rows[activeIndex].blocker = caption;\n\t\t}\n\t\tmatrix.rows = rows;\n\t\tmatrix.status = pass ? \"pass\" : \"failed\";\n\t\tmatrix.part_inventory_snapshot_proof = proofPath;\n\t\tmatrix.aiqa_business_assertion = businessAssertionPath;\n\t\tmatrix.updated_at = new Date().toISOString();\n\t\twriteJson(matrixPath, matrix);\n\t\treturn payload;\n\t} catch (error) {\n\t\treturn await writeFailed(error && (error.stack || error.message) || String(error));\n\t}\n}\n"]))).trim().split('\n');
2722
- }
2723
1580
  function buildResolveIORunnerQaWorkflowProbeScript() {
2724
- return __spreadArray(__spreadArray([
1581
+ return [
2725
1582
  '#!/usr/bin/env node',
2726
1583
  "'use strict';",
2727
1584
  '',
@@ -2731,7 +1588,7 @@ function buildResolveIORunnerQaWorkflowProbeScript() {
2731
1588
  'const path = require("path");',
2732
1589
  '',
2733
1590
  'if (isBuildLane()) {',
2734
- ' console.error("ResolveIO support lane guard: browser workflow probes are owned by the QA lane, not the 5.3 build lane.");',
1591
+ ' console.error("ResolveIO support lane guard: browser workflow probes are owned by the QA lane, not the build lane.");',
2735
1592
  ' process.exit(86);',
2736
1593
  '}',
2737
1594
  '',
@@ -2744,7 +1601,7 @@ function buildResolveIORunnerQaWorkflowProbeScript() {
2744
1601
  'const authBootstrapResultPath = path.join(artifactDir, "auth-bootstrap-result.json");',
2745
1602
  'const passScreenshotPath = path.join(artifactDir, "qa-workflow-route-ready.jpg");',
2746
1603
  'const failScreenshotPath = path.join(artifactDir, "qa-workflow-route-blocked.jpg");',
2747
- 'const clientUrl = stripTrailingSlash(process.env.RESOLVEIO_RUNNER_QA_CLIENT_URL || process.env.RESOLVEIO_SUPPORT_QA_CLIENT_URL || `http://localhost:${process.env.RESOLVEIO_SUPPORT_QA_CLIENT_PORT || "4200"}`);',
1604
+ 'const clientUrl = stripTrailingSlash(process.env.RESOLVEIO_RUNNER_QA_CLIENT_URL || process.env.RESOLVEIO_SUPPORT_QA_CLIENT_URL || "http://localhost:" + (process.env.RESOLVEIO_SUPPORT_QA_CLIENT_PORT || "4200"));',
2748
1605
  'const serverUrl = stripTrailingSlash(process.env.RESOLVEIO_RUNNER_QA_SERVER_URL || process.env.RESOLVEIO_SUPPORT_QA_SERVER_URL || "http://localhost:8080");',
2749
1606
  'const explicitQaUsername = process.env.RESOLVEIO_RUNNER_QA_AFFECTED_USER_EMAIL || process.env.RESOLVEIO_SUPPORT_QA_AFFECTED_USER_EMAIL || process.env.RESOLVEIO_RUNNER_QA_TICKET_REPORTER_EMAIL || process.env.RESOLVEIO_SUPPORT_QA_TICKET_REPORTER_EMAIL || "";',
2750
1607
  'const envQaUsername = process.env.RESOLVEIO_RUNNER_QA_USERNAME || process.env.RESOLVEIO_SUPPORT_QA_USERNAME || "";',
@@ -2753,855 +1610,42 @@ function buildResolveIORunnerQaWorkflowProbeScript() {
2753
1610
  'const password = process.env.RESOLVEIO_RUNNER_QA_PASSWORD || process.env.RESOLVEIO_SUPPORT_QA_PASSWORD || "";',
2754
1611
  'const viewportWidth = Number(process.env.RESOLVEIO_RUNNER_QA_VIEWPORT_WIDTH || process.env.RESOLVEIO_SUPPORT_QA_VIEWPORT_WIDTH || 1920);',
2755
1612
  'const viewportHeight = Number(process.env.RESOLVEIO_RUNNER_QA_VIEWPORT_HEIGHT || process.env.RESOLVEIO_SUPPORT_QA_VIEWPORT_HEIGHT || 1080);',
2756
- '',
2757
- 'function isBuildLane() {',
2758
- ' return /^(build|support_build|support-build)$/i.test(String(process.env.RESOLVEIO_SUPPORT_CODEX_LANE || "").trim());',
2759
- '}',
2760
- '',
1613
+ 'function isBuildLane() { return /^(build|support_build|support-build)$/i.test(String(process.env.RESOLVEIO_SUPPORT_CODEX_LANE || "").trim()); }',
2761
1614
  'function stripTrailingSlash(value) { return String(value || "").replace(/\\/+$/, ""); }',
2762
1615
  'function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }',
2763
1616
  'function writeJson(filePath, payload) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, JSON.stringify(payload, null, 2)); }',
2764
1617
  'function readJson(filePath) { try { return JSON.parse(fs.readFileSync(filePath, "utf8")); } catch (error) { return null; } }',
2765
- 'function readSeedHintText() {',
2766
- ' const repoRoot = path.dirname(projectRoot);',
2767
- ' const chunks = [',
2768
- ' process.env.RESOLVEIO_SUPPORT_QA_ROW_FILTER || "",',
2769
- ' process.env.RESOLVEIO_SUPPORT_QA_CURRENT_ROW_WORKFLOW || "",',
2770
- ' process.env.RESOLVEIO_SUPPORT_QA_CURRENT_ROW_ASSERTION || "",',
2771
- ' process.env.RESOLVEIO_RUNNER_QA_ROW_FILTER || "",',
2772
- ' process.env.RESOLVEIO_RUNNER_QA_CURRENT_ROW_WORKFLOW || "",',
2773
- ' process.env.RESOLVEIO_RUNNER_QA_CURRENT_ROW_ASSERTION || "",',
2774
- ' process.env.RESOLVEIO_QA_SEED_HINTS || "",',
2775
- ' process.env.RESOLVEIO_SUPPORT_QA_SEED_HINTS || "",',
2776
- ' process.env.RESOLVEIO_RUNNER_QA_SEED_HINTS || ""',
2777
- ' ];',
2778
- ' const candidates = [',
2779
- ' path.join(artifactDir, "qa-coverage-matrix.json"),',
2780
- ' path.join(artifactDir, "qa-row-lock.json"),',
2781
- ' path.join(repoRoot, ".resolveio-support-context", "manual-ticket.request.txt"),',
2782
- ' path.join(projectRoot, ".resolveio-support-context", "manual-ticket.request.txt"),',
2783
- ' path.join(repoRoot, ".resolveio-context", "manual-ticket.request.txt"),',
2784
- ' path.join(projectRoot, ".resolveio-context", "manual-ticket.request.txt")',
2785
- ' ];',
2786
- ' for (const candidate of candidates) {',
2787
- ' try {',
2788
- ' const raw = fs.readFileSync(candidate, "utf8");',
2789
- ' chunks.push(raw);',
2790
- ' if (/qa-coverage-matrix\\.json$/.test(candidate)) {',
2791
- ' const matrix = JSON.parse(raw);',
2792
- ' const rows = Array.isArray(matrix && matrix.rows) ? matrix.rows : [];',
2793
- ' for (const row of rows) chunks.push([row.workflow, row.route, row.assertion, row.required_proof, row.data_id, row.data_name, row.blocker, row.evidence, row.caption].filter(Boolean).join("\\n"));',
2794
- ' }',
2795
- ' if (/qa-row-lock\\.json$/.test(candidate)) {',
2796
- ' const lock = JSON.parse(raw);',
2797
- ' chunks.push([lock.workflow, lock.route, lock.assertion, lock.required_proof, lock.rowFilter, lock.blocker].filter(Boolean).join("\\n"));',
2798
- ' }',
2799
- ' } catch (error) {}',
2800
- ' }',
2801
- ' return chunks.filter(Boolean).join("\\n");',
2802
- '}',
2803
- 'function readSeededPreferredQaUsername() {',
2804
- ' try {',
2805
- ' const seed = readJson(path.join(artifactDir, "qa-live-data-seed-result.json"));',
2806
- ' const context = seed && seed.selected && seed.selected.qa_user_context;',
2807
- ' const preferred = context && (context.preferred_username || context.preferred_email || context.affected_user_email || context.reporter_email);',
2808
- ' return String(preferred || "").trim();',
2809
- ' } catch (error) {',
2810
- ' return "";',
2811
- ' }',
2812
- '}',
1618
+ 'function readSeededPreferredQaUsername() { try { const seed = readJson(path.join(artifactDir, "qa-live-data-seed-result.json")); const context = seed && seed.selected && seed.selected.qa_user_context; const preferred = context && (context.preferred_username || context.preferred_email || context.affected_user_email || context.reporter_email); return String(preferred || "").trim(); } catch (error) { return ""; } }',
2813
1619
  'function normalizeRoute(candidate) {',
2814
- ' const value = String(candidate || "").trim();',
2815
- ' if (!value) return "";',
2816
- ' let routeText = value;',
2817
- ' if (/^https?:\\/\\//i.test(routeText)) {',
2818
- ' try { routeText = new URL(routeText).pathname || "/"; } catch (error) { return ""; }',
2819
- ' }',
2820
- " const match = routeText.match(/(?:^|[\\s\\\"'`(])((?:\\/[a-z0-9][a-z0-9._~!$&'()*+,;=:@%\\/-]*)(?:\\?[^\\s\\\"'`<>)]*)?)/i);",
2821
- ' routeText = ((match && match[1]) || routeText).split(/\\s*->\\s*/)[0].split(/\\s+-\\s+/)[0].trim().replace(/[),.;]+$/g, "");',
2822
- ' if (!routeText) return "";',
2823
- ' return routeText.startsWith("/") ? routeText : `/${routeText}`;',
2824
- '}',
2825
- 'function resolveTargetRoute(explicitRoute) {',
2826
- ' const hintText = readSeedHintText();',
2827
- ' const preferInterchangeables = /\\b(interchangeable|interchangeables|mass\\s+update|update\\s+interchangeables|from\\s+chemical|to\\s+chemical|treatment\\s+plan|treatment\\s+plans|production\\s+location)\\b/i.test(hintText);',
2828
- ' const seedCandidates = [',
2829
- ' path.join(artifactDir, "qa-live-data-seed-result.json"),',
2830
- ' path.join(artifactDir, "candidate", "qa-live-data-seed-result.json")',
2831
- ' ];',
2832
- ' for (const seedPath of seedCandidates) {',
2833
- ' const seed = readJson(seedPath);',
2834
- ' const interchangeablesRoutes = (seed && seed.selected && seed.selected.qa_production_interchangeables_context && seed.selected.qa_production_interchangeables_context.browser_routes) || (seed && seed.qa_production_interchangeables_context && seed.qa_production_interchangeables_context.browser_routes);',
2835
- ' const interchangeablesRoute = normalizeRoute(interchangeablesRoutes && (interchangeablesRoutes.update_interchangeables || interchangeablesRoutes.detail || interchangeablesRoutes.list));',
2836
- ' if (preferInterchangeables && interchangeablesRoute) return interchangeablesRoute;',
2837
- ' const routes = seed && seed.selected && seed.selected.truck_treating_bol_context && seed.selected.truck_treating_bol_context.browser_routes;',
2838
- ' const route = normalizeRoute(routes && (routes.delivery || routes.detail || routes.list));',
2839
- ' if (!preferInterchangeables && route) return route;',
2840
- ' }',
2841
- ' if (preferInterchangeables) return "/production-location/update-interchangeables";',
2842
- ' const explicit = normalizeRoute(explicitRoute);',
2843
- ' if (explicit) return explicit;',
2844
- ' return "/";',
2845
- '}',
2846
- 'function requestReady(url) {',
2847
- ' return new Promise((resolve) => {',
2848
- ' try {',
2849
- ' const parsed = new URL(url);',
2850
- ' const mod = parsed.protocol === "https:" ? https : http;',
2851
- ' const req = mod.get(parsed, { timeout: 3000 }, (res) => { res.resume(); resolve(Boolean(res.statusCode && res.statusCode < 500)); });',
2852
- ' req.on("timeout", () => req.destroy(new Error("timeout")));',
2853
- ' req.on("error", () => resolve(false));',
2854
- ' } catch (error) { resolve(false); }',
2855
- ' });',
2856
- '}',
2857
- 'async function waitForHttpReady(url, label) {',
2858
- ' const deadline = Date.now() + 45000;',
2859
- ' while (Date.now() < deadline) {',
2860
- ' if (await requestReady(url)) return;',
2861
- ' await delay(1500);',
2862
- ' }',
2863
- ' throw new Error(`${label} did not become ready at ${url}`);',
2864
- '}',
2865
- 'function requestJson(url, payload) {',
2866
- ' return new Promise((resolve, reject) => {',
2867
- ' const body = JSON.stringify(payload || {});',
2868
- ' const parsed = new URL(url);',
2869
- ' const mod = parsed.protocol === "https:" ? https : http;',
2870
- ' const req = mod.request(parsed, { method: "POST", timeout: 20000, headers: { "content-type": "application/json", "content-length": Buffer.byteLength(body), "origin": clientUrl } }, (res) => {',
2871
- ' let raw = "";',
2872
- ' res.setEncoding("utf8");',
2873
- ' res.on("data", (chunk) => { raw += chunk; });',
2874
- ' res.on("end", () => {',
2875
- ' let json = null;',
2876
- ' try { json = raw ? JSON.parse(raw) : {}; } catch (error) { reject(new Error(`${url} returned non-JSON HTTP ${res.statusCode}: ${raw.slice(0, 300)}`)); return; }',
2877
- ' if (!res.statusCode || res.statusCode >= 400) { reject(new Error(`${url} returned HTTP ${res.statusCode}: ${JSON.stringify(json).slice(0, 500)}`)); return; }',
2878
- ' resolve(json);',
2879
- ' });',
2880
- ' });',
2881
- ' req.on("timeout", () => req.destroy(new Error(`${url} timed out`)));',
2882
- ' req.on("error", reject);',
2883
- ' req.write(body);',
2884
- ' req.end();',
2885
- ' });',
2886
- '}',
2887
- 'function requirePuppeteer() {',
2888
- ' const candidates = [',
2889
- ' path.join(projectRoot, "server", "node_modules", "puppeteer", "lib", "cjs", "puppeteer", "puppeteer.js"),',
2890
- ' path.join(projectRoot, "node_modules", "puppeteer", "lib", "cjs", "puppeteer", "puppeteer.js"),',
2891
- ' path.join(process.cwd(), "server", "node_modules", "puppeteer", "lib", "cjs", "puppeteer", "puppeteer.js"),',
2892
- ' path.join(process.cwd(), "node_modules", "puppeteer", "lib", "cjs", "puppeteer", "puppeteer.js"),',
2893
- ' "puppeteer"',
2894
- ' ];',
2895
- ' for (const candidate of candidates) { try { return require(candidate); } catch (error) {} }',
2896
- ' throw new Error("Unable to require puppeteer from project/server node_modules or global resolution");',
2897
- '}',
2898
- 'async function launchBrowser(puppeteer) {',
2899
- ' const launchOptions = { headless: true, defaultViewport: { width: viewportWidth, height: viewportHeight }, args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", `--window-size=${viewportWidth},${viewportHeight}`] };',
2900
- ' if (process.env.PUPPETEER_EXECUTABLE_PATH || process.env.CHROME_BIN) launchOptions.executablePath = process.env.PUPPETEER_EXECUTABLE_PATH || process.env.CHROME_BIN;',
2901
- ' return puppeteer.launch(launchOptions);',
2902
- '}',
2903
- 'async function login() {',
2904
- ' if (!password) throw new Error("QA password is empty; source the generated env.sh before workflow probe");',
2905
- ' const loginJson = await requestJson(`${serverUrl}/login`, { username, password });',
2906
- ' const refreshToken = loginJson && loginJson.result && loginJson.result.token;',
2907
- ' if (loginJson.error || !refreshToken) throw new Error(`Login failed: ${JSON.stringify(loginJson).slice(0, 800)}`);',
2908
- ' const accessJson = await requestJson(`${serverUrl}/accessToken`, { refreshToken });',
2909
- ' const accessToken = accessJson && accessJson.result && accessJson.result.token;',
2910
- ' const user = accessJson && accessJson.result && accessJson.result.user;',
2911
- ' if (accessJson.error || !accessToken || !user) throw new Error(`Access token failed: ${JSON.stringify(accessJson).slice(0, 800)}`);',
2912
- ' return { refreshToken, accessToken, user };',
2913
- '}',
2914
- 'async function seedAuth(page, auth) {',
2915
- ' await page.goto(clientUrl, { waitUntil: "domcontentloaded", timeout: 45000 });',
2916
- ' await page.evaluate((payload) => {',
2917
- ' localStorage.clear();',
2918
- ' sessionStorage.clear();',
2919
- ' localStorage.setItem("refreshToken", payload.refreshToken);',
2920
- ' localStorage.setItem("accessToken", payload.accessToken);',
2921
- ' localStorage.setItem("user", JSON.stringify({ ...(payload.user || {}), other: { ...((payload.user || {}).other || {}), tour_completed: true, took_tour: true }, settings: { ...((payload.user || {}).settings || {}), collapsable_menu: false, opening_route: payload.targetRoute } }));',
2922
- ' localStorage.setItem("lastURL", payload.targetRoute);',
2923
- ' }, { ...auth, targetRoute });',
2924
- '}',
2925
- 'async function pageSummary(page) {',
2926
- ' return page.evaluate(() => {',
2927
- ' const bodyText = (document.body && document.body.innerText || "").replace(/\\s+/g, " ").trim();',
2928
- ' const rows = Array.from(document.querySelectorAll("tr")).map(row => (row.innerText || "").replace(/\\s+/g, " ").trim()).filter(Boolean);',
2929
- ' return { url: location.href, title: document.title, bodyTextSnippet: bodyText.slice(0, 1200), rowCount: rows.length, rows: rows.slice(0, 8), hasLoginText: /Employee\\/Customer Login|Employee Sign In|Customer Access|Unable to sign in/i.test(bodyText), hasOfflineModeText: bodyText.includes("*** OFFLINE MODE ***"), storageState: { hasRefreshToken: !!localStorage.getItem("refreshToken"), hasAccessToken: !!localStorage.getItem("accessToken"), hasUser: !!localStorage.getItem("user") } };',
2930
- ' });',
2931
- '}',
2932
- 'function pathMatchesRoute(currentUrl, expectedRoute) {',
2933
- ' try {',
2934
- ' const current = new URL(currentUrl);',
2935
- ' const expected = new URL(expectedRoute, clientUrl);',
2936
- ' return current.pathname.replace(/\\/+$/, "") === expected.pathname.replace(/\\/+$/, "");',
2937
- ' } catch (error) {',
2938
- ' return false;',
2939
- ' }',
2940
- '}',
2941
- 'async function waitForAuthenticatedApp(page) {',
2942
- ' await page.goto(`${clientUrl}/home`, { waitUntil: "domcontentloaded", timeout: 60000 });',
2943
- ' const deadline = Date.now() + Number(process.env.RESOLVEIO_RUNNER_QA_AUTH_WARMUP_TIMEOUT_MS || process.env.RESOLVEIO_SUPPORT_QA_AUTH_WARMUP_TIMEOUT_MS || 45000);',
2944
- ' let summary = await pageSummary(page);',
2945
- ' while (Date.now() < deadline) {',
2946
- ' summary = await pageSummary(page);',
2947
- ' if (!summary.hasLoginText && !summary.hasOfflineModeText && summary.storageState.hasRefreshToken && summary.storageState.hasAccessToken && summary.storageState.hasUser) {',
2948
- ' await delay(Number(process.env.RESOLVEIO_RUNNER_QA_POST_AUTH_SETTLE_MS || process.env.RESOLVEIO_SUPPORT_QA_POST_AUTH_SETTLE_MS || 2500));',
2949
- ' return await pageSummary(page);',
2950
- ' }',
2951
- ' await delay(1000);',
2952
- ' }',
2953
- ' throw new Error(`QA auth warmup failed before target route. Page summary: ${JSON.stringify(summary).slice(0, 1000)}`);',
2954
- '}',
2955
- 'function isShellOnlySummary(summary) {',
2956
- ' const text = summary && summary.bodyTextSnippet || "";',
2957
- ' if (!text) return true;',
2958
- ' if (summary && Number(summary.rowCount || 0) > 0) return false;',
2959
- ' if (/\\b(List|Detail|Edit|New|Filter|Search|Showing|records|CLASSIFICATION|STATUS|Entries Per Page|Dashboard|Report|Invoice|Asset|BOL)\\b/i.test(text)) return false;',
2960
- ' return /^(Home|MENU|Logout|\\u00A9|The All-in-One Software)\\b/i.test(text) && text.length < 350;',
2961
- '}',
2962
- 'function freshAuthBootstrapProof() {',
2963
- ' const proof = readJson(authBootstrapResultPath);',
2964
- ' if (!proof || proof.status !== "pass" || !proof.page) return null;',
2965
- ' if (!pathMatchesRoute(proof.page.url || "", targetRoute)) return null;',
2966
- ' if (proof.targetRoute && !pathMatchesRoute(new URL(proof.targetRoute, clientUrl).href, targetRoute)) return null;',
2967
- ' if (isShellOnlySummary(proof.page)) return null;',
2968
- ' const text = proof.page.bodyTextSnippet || "";',
2969
- ' if (!text || /Employee\\/Customer Login|Employee Sign In|Customer Access|Unable to sign in/i.test(text)) return null;',
2970
- ' if (text.includes("*** OFFLINE MODE ***")) return null;',
2971
- ' try {',
2972
- ' const stat = fs.statSync(authBootstrapResultPath);',
2973
- ' const maxAgeMs = Number(process.env.RESOLVEIO_RUNNER_QA_AUTH_PROOF_MAX_AGE_MS || process.env.RESOLVEIO_SUPPORT_QA_AUTH_PROOF_MAX_AGE_MS || 300000);',
2974
- ' if (Date.now() - stat.mtimeMs > maxAgeMs) return null;',
2975
- ' } catch (error) { return null; }',
2976
- ' return proof;',
2977
- '}',
2978
- 'async function waitForHydratedTargetRoute(page) {',
2979
- ' const deadline = Date.now() + Number(process.env.RESOLVEIO_RUNNER_QA_ROUTE_HYDRATION_TIMEOUT_MS || process.env.RESOLVEIO_SUPPORT_QA_ROUTE_HYDRATION_TIMEOUT_MS || 45000);',
2980
- ' let summary = await pageSummary(page);',
2981
- ' while (Date.now() < deadline) {',
2982
- ' summary = await pageSummary(page);',
2983
- ' const routeOk = pathMatchesRoute(summary.url, targetRoute);',
2984
- ' const shellOnly = isShellOnlySummary(summary);',
2985
- ' if (routeOk && !summary.hasLoginText && !summary.hasOfflineModeText && !shellOnly) {',
2986
- ' return summary;',
2987
- ' }',
2988
- ' if (summary.hasLoginText || summary.hasOfflineModeText || !routeOk || shellOnly) {',
2989
- ' await delay(1500);',
2990
- ' await page.goto(new URL(targetRoute, clientUrl).href, { waitUntil: "domcontentloaded", timeout: 60000 }).catch(() => null);',
2991
- ' } else {',
2992
- ' await delay(1500);',
2993
- ' }',
2994
- ' }',
2995
- ' throw new Error(`QA route hydration failed for ${targetRoute}. Page summary: ${JSON.stringify(summary).slice(0, 1000)}`);',
2996
- '}',
2997
- 'function billingDashboardHasVisibleWork(text) {',
2998
- ' const value = String(text || "");',
2999
- ' const patterns = [',
3000
- ' /PSO Type Invoice Awaiting Invoices, Customers: \\((\\d+)\\), Deliveries: \\((\\d+)\\)/i,',
3001
- ' /Deliveries Awaiting Invoices, Customers: \\((\\d+)\\), Treatments: \\((\\d+)\\)/i,',
3002
- ' /Truck Treatments Awaiting Invoices, Customers: \\((\\d+)\\), Treatments: \\((\\d+)\\)/i,',
3003
- ' /Periodic Billing, Customers: \\((\\d+)\\), Items: \\((\\d+)\\)/i,',
3004
- ' /Custom Consolidated Invoicing, Customers: \\((\\d+)\\), Items: \\((\\d+)\\)/i,',
3005
- ' /Processing, Customers: \\((\\d+)\\), Invoices: \\((\\d+)\\)/i,',
3006
- ' /Prepared, Customers: \\((\\d+)\\), Invoices: \\((\\d+)\\)/i,',
3007
- ' /Open Invoices, Customers: \\((\\d+)\\), Invoices: \\((\\d+)\\)/i',
3008
- ' ];',
3009
- ' for (const pattern of patterns) {',
3010
- ' const match = pattern.exec(value);',
3011
- ' if (match && (Number(match[1]) > 0 || Number(match[2]) > 0)) return true;',
3012
- ' }',
3013
- ' return false;',
3014
- '}',
3015
- 'async function waitForBillingDashboardWork(page) {',
3016
- ' if (!/\\/billing(?:$|[?#])|\\/dashboard\\/billing(?:$|[?#])/.test(targetRoute)) return null;',
3017
- ' const deadline = Date.now() + Number(process.env.RESOLVEIO_RUNNER_QA_BILLING_DATA_TIMEOUT_MS || process.env.RESOLVEIO_SUPPORT_QA_BILLING_DATA_TIMEOUT_MS || 45000);',
3018
- ' let summary = await pageSummary(page);',
3019
- ' while (Date.now() < deadline) {',
3020
- ' summary = await pageSummary(page);',
3021
- ' if (billingDashboardHasVisibleWork(summary.bodyTextSnippet)) return summary;',
3022
- ' await delay(1500);',
3023
- ' }',
3024
- ' throw new Error(`Billing Dashboard loaded but no actionable seeded billing rows became visible before QA handoff. This is a runner data-seeding blocker, not a feature failure. Page summary: ${JSON.stringify(summary).slice(0, 1000)}`);',
3025
- '}',
1620
+ ' const value = String(candidate || "").trim();',
1621
+ ' if (!value) return "";',
1622
+ ' let routeText = value;',
1623
+ ' if (/^https?:\\/\\//i.test(routeText)) { try { routeText = new URL(routeText).pathname || "/"; } catch (error) { return ""; } }',
1624
+ " const match = routeText.match(/(?:^|[\\s\\\"'`(])((?:\\/[a-z0-9][a-z0-9._~!$&'()*+,;=:@%\\/-]*)(?:\\?[^\\s\\\"'`<>)]*)?)/i);",
1625
+ ' routeText = ((match && match[1]) || routeText).split(/\\s*->\\s*/)[0].split(/\\s+-\\s+/)[0].trim().replace(/[),.;]+$/g, "");',
1626
+ ' if (!routeText) return "";',
1627
+ ' return routeText.startsWith("/") ? routeText : "/" + routeText;',
1628
+ '}',
1629
+ 'function resolveTargetRoute(explicitRoute) { const explicit = normalizeRoute(explicitRoute); if (explicit) return explicit; return "/"; }',
1630
+ 'function requestReady(url) { return new Promise((resolve) => { try { const parsed = new URL(url); const mod = parsed.protocol === "https:" ? https : http; const req = mod.get(parsed, { timeout: 3000 }, (res) => { res.resume(); resolve(Boolean(res.statusCode && res.statusCode < 500)); }); req.on("timeout", () => req.destroy(new Error("timeout"))); req.on("error", () => resolve(false)); } catch (error) { resolve(false); } }); }',
1631
+ 'async function waitForHttpReady(url, label) { const deadline = Date.now() + 45000; while (Date.now() < deadline) { if (await requestReady(url)) return; await delay(1500); } throw new Error(label + " did not become ready at " + url); }',
1632
+ 'function requestJson(url, payload) { return new Promise((resolve, reject) => { const body = JSON.stringify(payload || {}); const parsed = new URL(url); const mod = parsed.protocol === "https:" ? https : http; const req = mod.request(parsed, { method: "POST", timeout: 20000, headers: { "content-type": "application/json", "content-length": Buffer.byteLength(body), "origin": clientUrl } }, (res) => { let raw = ""; res.setEncoding("utf8"); res.on("data", (chunk) => { raw += chunk; }); res.on("end", () => { let json = null; try { json = raw ? JSON.parse(raw) : {}; } catch (error) { reject(new Error(url + " returned non-JSON HTTP " + res.statusCode + ": " + raw.slice(0, 300))); return; } if (!res.statusCode || res.statusCode >= 400) { reject(new Error(url + " returned HTTP " + res.statusCode + ": " + JSON.stringify(json).slice(0, 500))); return; } resolve(json); }); }); req.on("timeout", () => req.destroy(new Error(url + " timed out"))); req.on("error", reject); req.write(body); req.end(); }); }',
1633
+ 'function requirePuppeteer() { const candidates = [path.join(projectRoot, "server", "node_modules", "puppeteer", "lib", "cjs", "puppeteer", "puppeteer.js"), path.join(projectRoot, "node_modules", "puppeteer", "lib", "cjs", "puppeteer", "puppeteer.js"), path.join(process.cwd(), "server", "node_modules", "puppeteer", "lib", "cjs", "puppeteer", "puppeteer.js"), path.join(process.cwd(), "node_modules", "puppeteer", "lib", "cjs", "puppeteer", "puppeteer.js"), "puppeteer"]; for (const candidate of candidates) { try { return require(candidate); } catch (error) {} } throw new Error("Unable to require puppeteer from project/server node_modules or global resolution"); }',
1634
+ 'async function launchBrowser(puppeteer) { const launchOptions = { headless: true, defaultViewport: { width: viewportWidth, height: viewportHeight }, args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", "--window-size=" + viewportWidth + "," + viewportHeight] }; if (process.env.PUPPETEER_EXECUTABLE_PATH || process.env.CHROME_BIN) launchOptions.executablePath = process.env.PUPPETEER_EXECUTABLE_PATH || process.env.CHROME_BIN; return puppeteer.launch(launchOptions); }',
1635
+ 'async function login() { if (!password) throw new Error("QA password is empty; source the generated env.sh before workflow probe"); const loginJson = await requestJson(serverUrl + "/login", { username, password }); const refreshToken = loginJson && loginJson.result && loginJson.result.token; if (loginJson.error || !refreshToken) throw new Error("Login failed: " + JSON.stringify(loginJson).slice(0, 800)); const accessJson = await requestJson(serverUrl + "/accessToken", { refreshToken }); const accessToken = accessJson && accessJson.result && accessJson.result.token; const user = accessJson && accessJson.result && accessJson.result.user; if (accessJson.error || !accessToken || !user) throw new Error("Access token failed: " + JSON.stringify(accessJson).slice(0, 800)); return { refreshToken, accessToken, user }; }',
1636
+ 'async function seedAuth(page, auth) { await page.goto(clientUrl, { waitUntil: "domcontentloaded", timeout: 45000 }); await page.evaluate((payload) => { localStorage.clear(); sessionStorage.clear(); localStorage.setItem("refreshToken", payload.refreshToken); localStorage.setItem("accessToken", payload.accessToken); localStorage.setItem("user", JSON.stringify({ ...(payload.user || {}), other: { ...((payload.user || {}).other || {}), tour_completed: true, took_tour: true }, settings: { ...((payload.user || {}).settings || {}), collapsable_menu: false, opening_route: payload.targetRoute } })); localStorage.setItem("lastURL", payload.targetRoute); }, { ...auth, targetRoute }); }',
1637
+ 'async function pageSummary(page) { return page.evaluate(() => { const bodyText = (document.body && document.body.innerText || "").replace(/\\s+/g, " ").trim(); const rows = Array.from(document.querySelectorAll("tr")).map(row => (row.innerText || "").replace(/\\s+/g, " ").trim()).filter(Boolean); return { url: location.href, title: document.title, bodyTextSnippet: bodyText.slice(0, 1200), rowCount: rows.length, rows: rows.slice(0, 8), hasLoginText: /Employee\\/Customer Login|Employee Sign In|Customer Access|Unable to sign in/i.test(bodyText), hasOfflineModeText: bodyText.includes("*** OFFLINE MODE ***"), storageState: { hasRefreshToken: !!localStorage.getItem("refreshToken"), hasAccessToken: !!localStorage.getItem("accessToken"), hasUser: !!localStorage.getItem("user") } }; }); }',
1638
+ 'function pathMatchesRoute(currentUrl, expectedRoute) { try { const current = new URL(currentUrl); const expected = new URL(expectedRoute, clientUrl); return current.pathname.replace(/\\/+$/, "") === expected.pathname.replace(/\\/+$/, ""); } catch (error) { return false; } }',
1639
+ 'async function waitForAuthenticatedApp(page) { const deadline = Date.now() + Number(process.env.RESOLVEIO_RUNNER_QA_AUTH_WARMUP_TIMEOUT_MS || process.env.RESOLVEIO_SUPPORT_QA_AUTH_WARMUP_TIMEOUT_MS || 45000); await page.goto(clientUrl + "/home", { waitUntil: "domcontentloaded", timeout: 60000 }); let summary = await pageSummary(page); while (Date.now() < deadline) { summary = await pageSummary(page); if (!summary.hasLoginText && !summary.hasOfflineModeText && summary.storageState.hasRefreshToken && summary.storageState.hasAccessToken && summary.storageState.hasUser) { await delay(Number(process.env.RESOLVEIO_RUNNER_QA_POST_AUTH_SETTLE_MS || process.env.RESOLVEIO_SUPPORT_QA_POST_AUTH_SETTLE_MS || 2500)); return await pageSummary(page); } await delay(1000); } throw new Error("QA auth warmup failed before target route. Page summary: " + JSON.stringify(summary).slice(0, 1000)); }',
1640
+ 'function isShellOnlySummary(summary) { const text = summary && summary.bodyTextSnippet || ""; if (!text) return true; if (summary && Number(summary.rowCount || 0) > 0) return false; if (/\\b(List|Detail|Edit|New|Filter|Search|Showing|records|CLASSIFICATION|STATUS|Entries Per Page|Dashboard)\\b/i.test(text)) return false; return /^(Home|MENU|Logout|\\u00A9|The All-in-One Software)\\b/i.test(text) && text.length < 350; }',
1641
+ 'function freshAuthBootstrapProof() { const proof = readJson(authBootstrapResultPath); if (!proof || proof.status !== "pass" || !proof.page) return null; if (!pathMatchesRoute(proof.page.url || "", targetRoute)) return null; if (proof.targetRoute && !pathMatchesRoute(new URL(proof.targetRoute, clientUrl).href, targetRoute)) return null; if (isShellOnlySummary(proof.page)) return null; const text = proof.page.bodyTextSnippet || ""; if (!text || /Employee\\/Customer Login|Employee Sign In|Customer Access|Unable to sign in/i.test(text)) return null; if (text.includes("*** OFFLINE MODE ***")) return null; try { const stat = fs.statSync(authBootstrapResultPath); const maxAgeMs = Number(process.env.RESOLVEIO_RUNNER_QA_AUTH_PROOF_MAX_AGE_MS || process.env.RESOLVEIO_SUPPORT_QA_AUTH_PROOF_MAX_AGE_MS || 300000); if (Date.now() - stat.mtimeMs > maxAgeMs) return null; } catch (error) { return null; } return proof; }',
1642
+ 'async function waitForHydratedTargetRoute(page) { const deadline = Date.now() + Number(process.env.RESOLVEIO_RUNNER_QA_ROUTE_HYDRATION_TIMEOUT_MS || process.env.RESOLVEIO_SUPPORT_QA_ROUTE_HYDRATION_TIMEOUT_MS || 45000); let summary = await pageSummary(page); while (Date.now() < deadline) { summary = await pageSummary(page); const routeOk = pathMatchesRoute(summary.url, targetRoute); const shellOnly = isShellOnlySummary(summary); if (routeOk && !summary.hasLoginText && !summary.hasOfflineModeText && !shellOnly) return summary; await delay(1500); await page.goto(new URL(targetRoute, clientUrl).href, { waitUntil: "domcontentloaded", timeout: 60000 }).catch(() => null); } throw new Error("QA route hydration failed for " + targetRoute + ". Page summary: " + JSON.stringify(summary).slice(0, 1000)); }',
1643
+ 'async function waitForRouteSpecificBusinessState(page) { void page; return null; }',
3026
1644
  'function matrixRows(matrix) { return Array.isArray(matrix && matrix.rows) ? matrix.rows : []; }',
3027
- 'function activeRowText() {',
3028
- ' const matrix = readJson(matrixPath) || {};',
3029
- ' const row = matrixRows(matrix).find((candidate) => !/^(pass|passed)$/i.test(String(candidate && candidate.status || ""))) || matrixRows(matrix)[0] || {};',
3030
- ' const rowText = [row.workflow, row.route, row.assertion, row.required_proof].map((value) => String(value || "")).join("\\n");',
3031
- ' return rowText.trim() ? rowText : readSeedHintText();',
3032
- '}',
3033
- 'function readSeedAssetContext() {',
3034
- ' const seed = readJson(path.join(artifactDir, "qa-live-data-seed-result.json"));',
3035
- ' const context = seed && seed.selected && seed.selected.qa_asset_context;',
3036
- ' if (!context || !Array.isArray(context.asset_ids) || !context.asset_ids.length) return null;',
3037
- ' return context;',
3038
- '}',
3039
- 'function depsCacheModuleCandidates(moduleName) {',
3040
- ' const roots = uniqueStrings([',
3041
- ' process.env.RESOLVEIO_RUNNER_QA_DEPS_CACHE_ROOT,',
3042
- ' process.env.RESOLVEIO_SUPPORT_QA_DEPS_CACHE_ROOT,',
3043
- ' path.join(projectRoot, ".deps-cache"),',
3044
- ' path.join(path.dirname(projectRoot), ".deps-cache"),',
3045
- ' path.join(path.dirname(path.dirname(projectRoot)), ".deps-cache"),',
3046
- ' "/var/app/resolveio-ai-workspace/.deps-cache/resolveio-all"',
3047
- ' ]);',
3048
- ' const candidates = [];',
3049
- ' for (const root of roots) {',
3050
- ' try {',
3051
- ' if (!root || !fs.existsSync(root)) continue;',
3052
- ' for (const entry of fs.readdirSync(root)) {',
3053
- ' candidates.push(path.join(root, entry, "node_modules", moduleName));',
3054
- ' candidates.push(path.join(root, entry, "node_modules", "@resolveio", "server-lib", "node_modules", moduleName));',
3055
- ' }',
3056
- ' } catch (error) {}',
3057
- ' }',
3058
- ' return candidates;',
3059
- '}',
3060
- 'function requireQaModule(moduleName) {',
3061
- ' const candidates = [',
3062
- ' path.join(projectRoot, "server", "node_modules", moduleName),',
3063
- ' path.join(projectRoot, "node_modules", moduleName),',
3064
- ' path.join(process.cwd(), "server", "node_modules", moduleName),',
3065
- ' path.join(process.cwd(), "node_modules", moduleName),',
3066
- ' ...depsCacheModuleCandidates(moduleName),',
3067
- ' moduleName',
3068
- ' ];',
3069
- ' for (const candidate of candidates) { try { return require(candidate); } catch (error) {} }',
3070
- ' throw new Error(`Unable to require ${moduleName} from project/server node_modules, QA dependency cache, or global resolution`);',
3071
- '}',
3072
- 'function mongoRequire() { return requireQaModule("mongodb"); }',
3073
- 'function xlsxRequire() { return requireQaModule("xlsx"); }',
3074
- 'function localMongoUrl() {',
3075
- ' return process.env.MONGO_URL || process.env.RESOLVEIO_RUNNER_QA_MONGO_URL || process.env.RESOLVEIO_SUPPORT_QA_MONGO_URL || `mongodb://127.0.0.1:${process.env.RESOLVEIO_SUPPORT_QA_MONGO_PORT || "3001"}/resolveio?directConnection=true`;',
3076
- '}',
3077
- 'function safeLocalMongoUrl() {',
3078
- ' const url = localMongoUrl();',
3079
- ' if (/^(true|1|yes|on)$/i.test(String(process.env.RESOLVEIO_SUPPORT_QA_ALLOW_NONLOCAL_MONGO_PROOF || process.env.RESOLVEIO_RUNNER_QA_ALLOW_NONLOCAL_MONGO_PROOF || ""))) return url;',
3080
- ' try {',
3081
- ' const parsed = new URL(url);',
3082
- ' const host = String(parsed.hostname || "").toLowerCase();',
3083
- ' if (host === "127.0.0.1" || host === "localhost" || host === "::1") return url;',
3084
- ' } catch (error) {}',
3085
- ' throw new Error(`Pricing import proof refuses to write to non-local Mongo URL: ${url.replace(/:[^:@/]+@/, ":***@")}`);',
3086
- '}',
3087
- 'function assetDisplayNumber(asset) { return String(asset && (asset.unit_number || asset.asset_number || asset.number || asset.name || asset._id) || ""); }',
3088
- 'function uniqueStrings(values) { return Array.from(new Set((values || []).map((value) => String(value || "").trim()).filter(Boolean))); }',
3089
- 'function staleLocationTerms(text) {',
3090
- ' const terms = [];',
3091
- ' const value = String(text || "");',
3092
- ' const staleMatch = /stale\\s+([^.;\\n]+)/i.exec(value);',
3093
- ' if (staleMatch) {',
3094
- ' for (const part of staleMatch[1].split(/,?\\s+and\\s+|\\s+or\\s+|;/i)) {',
3095
- ' const clean = part.replace(/\\b(absent|are|is|old|name|names)\\b/ig, " ").replace(/\\s+/g, " ").trim();',
3096
- ' if (/\\b[A-Z][A-Za-z]+(?:ton|port|city|ville|field)?\\b.*\\b[A-Z]{2}\\b/.test(clean)) terms.push(clean);',
3097
- ' }',
3098
- ' }',
3099
- ' for (const fallback of ["Jourdanton, TX", "Schreveport, LA", "Shreveport, LA"]) {',
3100
- ' if (new RegExp(fallback.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&"), "i").test(value)) terms.push(fallback);',
3101
- ' }',
3102
- ' return uniqueStrings(terms);',
3103
- '}',
3104
- 'async function fetchAssetLocationProofData(context) {',
3105
- ' const { MongoClient, ObjectId } = mongoRequire();',
3106
- ' const client = new MongoClient(localMongoUrl());',
3107
- ' await client.connect();',
3108
- ' try {',
3109
- ' const db = client.db();',
3110
- ' const assetIds = uniqueStrings(context.asset_ids);',
3111
- ' const assetNumbers = uniqueStrings(context.asset_numbers);',
3112
- ' const objectIds = assetIds.filter((id) => /^[a-f0-9]{24}$/i.test(id)).map((id) => new ObjectId(id));',
3113
- ' const query = { $or: [] };',
3114
- ' if (objectIds.length) query.$or.push({ _id: { $in: objectIds } });',
3115
- ' if (assetNumbers.length) query.$or.push({ unit_number: { $in: assetNumbers } }, { asset_number: { $in: assetNumbers } }, { number: { $in: assetNumbers } });',
3116
- ' const assets = query.$or.length ? await db.collection("assets").find(query).toArray() : [];',
3117
- ' const yardIds = uniqueStrings([...(context.yard_ids || []), ...assets.map((asset) => asset && asset.current_location && asset.current_location.id), ...assets.map((asset) => asset && asset.id_default_yard), ...assets.flatMap((asset) => Array.isArray(asset && asset.locations) ? asset.locations.map((location) => location && location.id) : [])]);',
3118
- ' const yardObjectIds = yardIds.filter((id) => /^[a-f0-9]{24}$/i.test(id)).map((id) => new ObjectId(id));',
3119
- ' const yardQuery = yardIds.length ? { $or: [{ _id: { $in: yardIds } }, ...(yardObjectIds.length ? [{ _id: { $in: yardObjectIds } }] : [])] } : null;',
3120
- ' const yards = yardQuery ? await db.collection("yards").find(yardQuery).toArray() : [];',
3121
- ' const yardById = new Map(yards.map((yard) => [String(yard._id), yard]));',
3122
- ' const rows = assets.map((asset) => {',
3123
- ' const currentId = asset && asset.current_location && asset.current_location.id ? String(asset.current_location.id) : "";',
3124
- ' const currentYard = currentId ? yardById.get(currentId) : null;',
3125
- ' const defaultYardName = String(asset.default_yard || "");',
3126
- ' const currentName = asset && asset.current_location && asset.current_location.name ? String(asset.current_location.name) : "";',
3127
- ' const canonicalName = currentYard && currentYard.name ? String(currentYard.name) : (defaultYardName || currentName);',
3128
- ' return { id: String(asset._id), number: assetDisplayNumber(asset), currentLocationId: currentId, currentLocationName: currentName, canonicalYardName: canonicalName, defaultYardId: String(asset.id_default_yard || ""), defaultYardName, pass: Boolean(canonicalName && currentName === canonicalName) };',
3129
- ' });',
3130
- ' return { assetIds, assetNumbers, rows, yards: yards.map((yard) => ({ id: String(yard._id), name: yard.name })) };',
3131
- ' } finally {',
3132
- ' await client.close().catch(() => undefined);',
3133
- ' }',
3134
- '}',
3135
- 'async function gotoAndSummarize(page, route, screenshotName, requiredTerms = []) {',
3136
- ' await page.goto(new URL(route, clientUrl).href, { waitUntil: "domcontentloaded", timeout: 60000 });',
3137
- ' await page.waitForSelector("body", { timeout: 30000 });',
3138
- ' let summary = await pageSummary(page);',
3139
- ' const deadline = Date.now() + Number(process.env.RESOLVEIO_SUPPORT_QA_ROW_WAIT_MS || 20000);',
3140
- ' while (Date.now() < deadline) {',
3141
- ' summary = await pageSummary(page);',
3142
- ' const body = String(summary.bodyTextSnippet || "");',
3143
- ' const hasTerms = requiredTerms.filter(Boolean).every((term) => new RegExp(String(term).replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&"), "i").test(body));',
3144
- ' if (hasTerms && !isShellOnlySummary(summary)) break;',
3145
- ' await delay(750);',
3146
- ' }',
3147
- ' await delay(Number(process.env.RESOLVEIO_RUNNER_QA_SCREENSHOT_SETTLE_MS || process.env.RESOLVEIO_SUPPORT_QA_SCREENSHOT_SETTLE_MS || 1000));',
3148
- ' await page.keyboard.press("Escape").catch(() => undefined);',
3149
- ' await delay(750);',
3150
- ' summary = await pageSummary(page);',
3151
- ' const screenshot = path.join(artifactDir, screenshotName);',
3152
- ' await page.screenshot({ path: screenshot, type: "jpeg", quality: 82, fullPage: false });',
3153
- ' return { route, screenshot, summary };',
3154
- '}',
3155
- 'function shouldRunPricingImportProof() {',
3156
- ' const seed = readJson(path.join(artifactDir, "qa-live-data-seed-result.json")) || {};',
3157
- ' if (String(seed.profile || "").toLowerCase() === "pricing_import") return true;',
3158
- ' const text = [activeRowText(), targetRoute].join("\\n");',
3159
- ' return /\\/manage\\/pricing(?:$|[/?#])/i.test(targetRoute) && /\\b(pricing|price|gross\\s+price|production\\s+price)\\b/i.test(text) && /\\b(import|upload|template|xlsx|excel|workbook|spreadsheet)\\b/i.test(text);',
3160
- '}',
3161
- 'function pricingWorkbookDirs() {',
3162
- ' const repoRoot = path.dirname(projectRoot);',
3163
- ' return uniqueStrings([',
3164
- ' path.join(projectRoot, ".resolveio-context", "attachments"),',
3165
- ' path.join(projectRoot, ".resolveio-support-context", "attachments"),',
3166
- ' path.join(repoRoot, ".resolveio-context", "attachments"),',
3167
- ' path.join(repoRoot, ".resolveio-support-context", "attachments"),',
3168
- ' path.join(artifactDir, "attachments")',
3169
- ' ]);',
3170
- '}',
3171
- 'function collectPricingImportWorkbooks() {',
3172
- ' const byKey = new Map();',
3173
- ' for (const dir of pricingWorkbookDirs()) {',
3174
- ' try {',
3175
- ' if (!fs.existsSync(dir)) continue;',
3176
- ' for (const name of fs.readdirSync(dir)) {',
3177
- ' if (!/\\.xlsx$/i.test(name) || /^~\\$/.test(name)) continue;',
3178
- ' const fullPath = path.join(dir, name);',
3179
- ' const stat = fs.statSync(fullPath);',
3180
- ' if (!stat.isFile()) continue;',
3181
- ' const key = `${stat.size}:${name.replace(/[^a-z0-9]+/gi, "").toLowerCase()}`;',
3182
- ' if (!byKey.has(key)) byKey.set(key, fullPath);',
3183
- ' }',
3184
- ' } catch (error) {}',
3185
- ' }',
3186
- ' return Array.from(byKey.values()).sort();',
3187
- '}',
3188
- 'function normalizeImportPriceRecord(line) {',
3189
- ' const source = line || {};',
3190
- ' const keyMap = {};',
3191
- ' for (const key of Object.keys(source)) keyMap[String(key).trim().toLowerCase().replace(/\\s+/g, " ")] = key;',
3192
- ' const getValue = (aliases) => { const alias = aliases.find((candidate) => keyMap[candidate] !== undefined); return alias ? source[keyMap[alias]] : undefined; };',
3193
- ' const normalized = {',
3194
- ' Customer: getValue(["customer"]),',
3195
- ' "Pricing Type": getValue(["pricing type", "type"]),',
3196
- ' Name: getValue(["name", "item", "chemical"]),',
3197
- ' Description: getValue(["description"]),',
3198
- ' "Gross Price": getValue(["gross price", "grossprice", "gross"]),',
3199
- ' "Discount Percent": getValue(["discount percent", "discount %", "discount", "discount percentage"]),',
3200
- ' Price: getValue(["price"])',
3201
- ' };',
3202
- ' if ((normalized["Gross Price"] === undefined || normalized["Gross Price"] === null || normalized["Gross Price"] === "") && normalized.Price !== undefined && normalized.Price !== null && normalized.Price !== "") normalized["Gross Price"] = normalized.Price;',
3203
- ' for (const key of Object.keys(normalized)) if (typeof normalized[key] === "string") normalized[key] = normalized[key].trim();',
3204
- ' return normalized;',
3205
- '}',
3206
- 'function cleanNumber(value) { return String(value === undefined || value === null ? "" : value).trim().replace(/(\\$|,|\\s)/g, ""); }',
3207
- 'function roundMoney(value) { return Math.round(Number(value || 0) * 100) / 100; }',
3208
- 'function lowerTrim(value) { return String(value === undefined || value === null ? "" : value).toLowerCase().trim(); }',
3209
- 'function idString(value) { return String(value && value.toHexString ? value.toHexString() : value || ""); }',
3210
- 'function pricingSubType(pricingType) {',
3211
- ' const type = lowerTrim(pricingType);',
3212
- ' if (type === "chemical") return "chemical";',
3213
- ' if (type === "service") return "service";',
3214
- ' if (type === "misc") return "misc";',
3215
- ' return "";',
3216
- '}',
3217
- 'async function readPricingImportContext(db) {',
3218
- ' const [customers, items, chemicals, users] = await Promise.all([',
3219
- ' db.collection("customers").find({}).project({ _id: 1, name: 1 }).toArray(),',
3220
- ' db.collection("items").find({}).project({ _id: 1, type: 1, name: 1, description: 1, id_chemical: 1 }).toArray(),',
3221
- ' db.collection("chemicals").find({}).project({ _id: 1, name: 1, description: 1, unit: 1, customers: 1 }).toArray(),',
3222
- ' db.collection("users").find({}).project({ _id: 1, fullname: 1, email: 1, username: 1 }).limit(5).toArray()',
3223
- ' ]);',
3224
- ' return { customers, items, chemicals, user: users[0] || null };',
3225
- '}',
3226
- 'function validatePricingImportRows(rawRows, context) {',
3227
- ' const validPriceRecords = [];',
3228
- ' const errorPriceRecords = [];',
3229
- ' const rows = rawRows.map((row, index) => ({ ...normalizeImportPriceRecord(row), __sourceRow: index + 2 }));',
3230
- ' for (const sample of rows) {',
3231
- ' const errors = [];',
3232
- ' const errorValues = [];',
3233
- ' sample.db_customer_id = "";',
3234
- ' sample.db_item_id = "";',
3235
- ' sample.db_chemical_id = "";',
3236
- ' sample.Customer = sample.Customer !== undefined && sample.Customer !== null ? String(sample.Customer).trim() : "";',
3237
- ' sample.Name = sample.Name !== undefined && sample.Name !== null ? String(sample.Name).trim() : "";',
3238
- ' sample.Description = sample.Description ? String(sample.Description).trim() : "";',
3239
- ' sample.Price = sample.Price !== undefined && sample.Price !== null ? String(sample.Price).trim() : "";',
3240
- ' if (sample.Customer) {',
3241
- ' const customer = context.customers.find((candidate) => lowerTrim(candidate.name) === lowerTrim(sample.Customer));',
3242
- ' if (customer) { sample.db_customer_id = idString(customer._id); sample.Customer = customer.name; }',
3243
- ' else if (sample.Customer === "Standard") sample.db_customer_id = "";',
3244
- ' else { errors.push("Customer not in the system"); errorValues.push(sample.Customer); }',
3245
- ' } else { errors.push("No Customer"); errorValues.push(sample.Customer); }',
3246
- ' if (!sample["Pricing Type"]) { errors.push("No Pricing Type"); errorValues.push(sample["Pricing Type"]); }',
3247
- ' else if (!/^(Chemical|Service|Misc)$/.test(String(sample["Pricing Type"]))) { errors.push("Invalid Pricing Type"); errorValues.push(sample["Pricing Type"]); }',
3248
- ' const item = sample.Name ? context.items.find((candidate) => lowerTrim(candidate.type) === lowerTrim(sample["Pricing Type"]) && lowerTrim(candidate.name) === lowerTrim(sample.Name)) : null;',
3249
- ' if (item) {',
3250
- ' sample.db_item_id = idString(item._id);',
3251
- ' sample.db_chemical_id = idString(item.id_chemical);',
3252
- ' sample.Name = item.name;',
3253
- ' sample.Description = item.description || sample.Description;',
3254
- ' } else if (sample.Name) { errors.push("Item not in the system"); errorValues.push(sample.Name); }',
3255
- ' else { errors.push("No Item"); errorValues.push(sample.Name); }',
3256
- ' const gross = cleanNumber(sample["Gross Price"]);',
3257
- ' if (gross) {',
3258
- ' if (!/^(\\d+\\.?\\d*|\\d*\\.?\\d+)$/.test(gross)) { errors.push("Price is not a valid number"); errorValues.push(gross); }',
3259
- ' else sample["Gross Price"] = gross;',
3260
- ' } else { errors.push("No Gross Price"); errorValues.push(sample["Gross Price"]); }',
3261
- ' const discount = cleanNumber(sample["Discount Percent"]);',
3262
- ' if (discount) {',
3263
- ' if (!/^(\\d+\\.?\\d*|\\d*\\.?\\d+)$/.test(discount)) { errors.push("Discount Percent is not a valid number"); errorValues.push(discount); }',
3264
- ' else sample["Discount Percent"] = discount;',
3265
- ' }',
3266
- ' if (errors.length) errorPriceRecords.push({ ...sample, errors, error_values: errorValues });',
3267
- ' else validPriceRecords.push(sample);',
3268
- ' }',
3269
- ' return { validPriceRecords, errorPriceRecords, totalRows: rawRows.length };',
3270
- '}',
3271
- 'function buildPricingImportPayload(validRows, context, sourceWorkbook, proofBatchId) {',
3272
- ' const { ObjectId } = mongoRequire();',
3273
- ' const user = context.user || {};',
3274
- ' return validRows.map((priceRecord) => {',
3275
- ' const subType = pricingSubType(priceRecord["Pricing Type"]);',
3276
- ' const item = context.items.find((candidate) => idString(candidate._id) === String(priceRecord.db_item_id));',
3277
- ' const chemical = priceRecord.db_chemical_id ? context.chemicals.find((candidate) => idString(candidate._id) === String(priceRecord.db_chemical_id)) : null;',
3278
- ' const grossPriceValue = Number(cleanNumber(priceRecord["Gross Price"] !== undefined && priceRecord["Gross Price"] !== null && priceRecord["Gross Price"] !== "" ? priceRecord["Gross Price"] : priceRecord.Price));',
3279
- ' const discountPercentValue = Number(cleanNumber(priceRecord["Discount Percent"] || 0)) || 0;',
3280
- ' return {',
3281
- ' _id: new ObjectId().toHexString(),',
3282
- ' __v: 0,',
3283
- ' type: "sale",',
3284
- ' sub_type: subType,',
3285
- ' date: new Date(),',
3286
- ' id_user: idString(user._id) || "qa-support-runner",',
3287
- ' user: user.fullname || user.email || user.username || "QA Support Runner",',
3288
- ' id_item: idString(item && item._id) || priceRecord.db_item_id || "",',
3289
- ' name: subType === "chemical" && chemical ? chemical.name : priceRecord.Name,',
3290
- ' description: subType === "chemical" && chemical ? (chemical.description || "") : (priceRecord.Description || ""),',
3291
- ' id_customer: priceRecord.Customer === "Standard" ? "" : (priceRecord.db_customer_id || ""),',
3292
- ' customer: priceRecord.Customer === "Standard" ? "Standard" : (priceRecord.Customer || ""),',
3293
- ' customer_type: "Production",',
3294
- ' id_chemical: chemical ? idString(chemical._id) : "",',
3295
- ' chemical: chemical ? chemical.name : "",',
3296
- ' unit: chemical && chemical.unit ? chemical.unit : "Each",',
3297
- ' price_gross: grossPriceValue,',
3298
- ' discount_percent: discountPercentValue,',
3299
- ' price: roundMoney(grossPriceValue * (1 - discountPercentValue / 100)),',
3300
- ' qa_support_pricing_import_proof: true,',
3301
- ' qa_proof_batch_id: proofBatchId,',
3302
- ' qa_source_workbook: path.basename(sourceWorkbook),',
3303
- ' qa_source_row: priceRecord.__sourceRow,',
3304
- ' qa_inserted_at: new Date()',
3305
- ' };',
3306
- ' });',
3307
- '}',
3308
- 'async function runPricingImportProof(page, routeSummary) {',
3309
- ' if (!shouldRunPricingImportProof()) return null;',
3310
- ' const proofPath = path.join(artifactDir, "qa-pricing-import-proof.json");',
3311
- ' const businessAssertionPath = path.join(artifactDir, "aiqa-business-assertion.json");',
3312
- ' const beforeAfterScreenshotPath = path.join(artifactDir, "support-business-proof-before-after.png");',
3313
- ' const startedAt = new Date().toISOString();',
3314
- ' const proofBatchId = `support-pricing-import-${Date.now()}-${Math.random().toString(16).slice(2)}`;',
3315
- ' const writePricingProof = (payload) => { writeJson(proofPath, payload); return payload; };',
3316
- ' const escapeHtml = (value) => String(value === undefined || value === null ? "" : value).replace(/[&<>"\']/g, (char) => { if (char === "&") return "&amp;"; if (char === "<") return "&lt;"; if (char === ">") return "&gt;"; if (char === "\\"") return "&quot;"; return "&#39;"; });',
3317
- ' const renderPricingProofScreenshot = async (payload, caption) => {',
3318
- ' const workbookRows = (payload.workbooks || []).map((entry) => `<tr><td>${escapeHtml(entry.name)}</td><td>${escapeHtml(entry.sheetName)}</td><td>${Number(entry.totalRows || 0)}</td><td class="ok">${Number(entry.validPriceRecords || 0)}</td><td>${Number(entry.errorPriceRecords || 0)}</td><td class="ok">${Number(entry.insertPricesFromImportPayloadSize || 0)}</td></tr>`).join("");',
3319
- ' const delta = payload.mongoDelta || {};',
3320
- ' const html = `<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:#f5f7fb;color:#111827;font-family:Arial,Helvetica,sans-serif}.wrap{padding:44px 56px}.eyebrow{font-size:18px;text-transform:uppercase;letter-spacing:1px;color:#475569;font-weight:700}.title{font-size:38px;font-weight:800;margin:10px 0 8px}.caption{font-size:22px;line-height:1.35;margin:0 0 26px;color:#1f2937}.grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:18px;margin:22px 0}.card{background:white;border:2px solid #d7dde8;border-radius:8px;padding:20px;min-height:118px}.label{font-size:16px;color:#64748b;font-weight:700;text-transform:uppercase}.value{font-size:34px;font-weight:800;margin-top:10px}.ok{color:#047857;font-weight:800}.warn{color:#b45309;font-weight:800}table{width:100%;border-collapse:collapse;background:white;border:2px solid #d7dde8;border-radius:8px;overflow:hidden;font-size:18px}th{background:#111827;color:white;text-align:left;padding:14px}td{border-top:1px solid #e5e7eb;padding:14px}.footer{margin-top:24px;font-size:18px;color:#475569}.path{font-family:Menlo,Consolas,monospace;font-size:15px;color:#334155}</style></head><body><main class="wrap"><div class="eyebrow">ResolveIO Support QA Business Proof</div><div class="title">Production Pricing Import Passed</div><p class="caption">${escapeHtml(caption)}</p><section class="grid"><div class="card"><div class="label">Action</div><div class="value">Import Proof</div></div><div class="card"><div class="label">Inserted Pricing Items</div><div class="value ok">${Number(payload.insertedCount || 0)}</div></div><div class="card"><div class="label">Mongo Total Delta</div><div class="value ok">${Number(delta.beforeTotalCount || 0)} -> ${Number(delta.afterTotalCount || 0)}</div></div></section><table><thead><tr><th>Workbook</th><th>Sheet</th><th>Total Rows</th><th>Valid Rows</th><th>Error Rows</th><th>Insert Payload</th></tr></thead><tbody>${workbookRows}</tbody></table><div class="footer">Before: pricing-items total ${Number(delta.beforeTotalCount || 0)}. Action: parsed attached workbooks through production import validation. After: pricing-items total ${Number(delta.afterTotalCount || 0)}, inserted ${Number(delta.inserted || 0)} QA proof records.</div><div class="footer path">Artifacts: qa-pricing-import-proof.json, aiqa-business-assertion.json, qa-coverage-matrix.json</div></main></body></html>`;',
3321
- ' await page.setViewport({ width: Math.max(viewportWidth, 1600), height: Math.max(viewportHeight, 900) }).catch(() => undefined);',
3322
- ' await page.setContent(html, { waitUntil: "domcontentloaded", timeout: 30000 });',
3323
- ' await page.screenshot({ path: beforeAfterScreenshotPath, type: "png", fullPage: false });',
3324
- ' };',
3325
- ' try {',
3326
- ' const workbookPaths = collectPricingImportWorkbooks();',
3327
- ' if (!workbookPaths.length) throw new Error("No attached pricing import .xlsx workbooks found in ResolveIO QA context attachments.");',
3328
- ' const xlsx = xlsxRequire();',
3329
- ' const { MongoClient } = mongoRequire();',
3330
- ' const client = new MongoClient(safeLocalMongoUrl());',
3331
- ' await client.connect();',
3332
- ' let payload;',
3333
- ' try {',
3334
- ' const db = client.db();',
3335
- ' const context = await readPricingImportContext(db);',
3336
- ' const workbookResults = [];',
3337
- ' const allInsertPayloads = [];',
3338
- ' for (const workbookPath of workbookPaths) {',
3339
- ' const workbook = xlsx.readFile(workbookPath, { cellDates: true });',
3340
- ' const sheetName = workbook.SheetNames.includes("Prices") ? "Prices" : workbook.SheetNames[0];',
3341
- ' const rows = sheetName ? xlsx.utils.sheet_to_json(workbook.Sheets[sheetName], { defval: "" }) : [];',
3342
- ' const validation = validatePricingImportRows(rows, context);',
3343
- ' const insertPayload = buildPricingImportPayload(validation.validPriceRecords, context, workbookPath, proofBatchId);',
3344
- ' allInsertPayloads.push(...insertPayload);',
3345
- ' workbookResults.push({',
3346
- ' workbook: workbookPath,',
3347
- ' name: path.basename(workbookPath),',
3348
- ' sheetName,',
3349
- ' totalRows: validation.totalRows,',
3350
- ' validPriceRecords: validation.validPriceRecords.length,',
3351
- ' errorPriceRecords: validation.errorPriceRecords.length,',
3352
- ' insertPricesFromImportPayloadSize: insertPayload.length,',
3353
- ' sampleValidRows: validation.validPriceRecords.slice(0, 5).map((row) => ({ sourceRow: row.__sourceRow, customer: row.Customer, pricingType: row["Pricing Type"], name: row.Name, grossPrice: row["Gross Price"], price: row.Price })),',
3354
- ' sampleErrors: validation.errorPriceRecords.slice(0, 5).map((row) => ({ sourceRow: row.__sourceRow, customer: row.Customer, pricingType: row["Pricing Type"], name: row.Name, errors: row.errors, error_values: row.error_values }))',
3355
- ' });',
3356
- ' }',
3357
- ' const blockedWorkbooks = workbookResults.filter((entry) => !entry.validPriceRecords || !entry.insertPricesFromImportPayloadSize);',
3358
- ' const pricingItems = db.collection("pricing-items");',
3359
- ' const beforeBatchCount = await pricingItems.countDocuments({ qa_support_pricing_import_proof: true, qa_proof_batch_id: proofBatchId });',
3360
- ' const beforeTotalCount = await pricingItems.countDocuments({});',
3361
- ' let insertResult = { insertedCount: 0, insertedIds: {} };',
3362
- ' if (!blockedWorkbooks.length && allInsertPayloads.length) insertResult = await pricingItems.insertMany(allInsertPayloads, { ordered: false });',
3363
- ' const afterBatchCount = await pricingItems.countDocuments({ qa_support_pricing_import_proof: true, qa_proof_batch_id: proofBatchId });',
3364
- ' const afterTotalCount = await pricingItems.countDocuments({});',
3365
- ' const insertedCount = Number(insertResult.insertedCount || 0);',
3366
- ' const pass = !blockedWorkbooks.length && allInsertPayloads.length > 0 && insertedCount === allInsertPayloads.length && afterBatchCount - beforeBatchCount === allInsertPayloads.length;',
3367
- ' const caption = pass ? `Pricing import proof passed: ${workbookResults.length} workbook(s), ${allInsertPayloads.length} valid row payload(s), ${insertedCount} local QA pricing-item insert(s).` : `Pricing import proof failed: ${blockedWorkbooks.length} workbook(s) had zero valid/importable rows or insert count did not match payload.`;',
3368
- ' payload = {',
3369
- ' status: pass ? "pass" : "failed",',
3370
- ' targetRoute,',
3371
- ' proofBatchId,',
3372
- ' startedAt,',
3373
- ' updated_at: new Date().toISOString(),',
3374
- ' workbooks: workbookResults,',
3375
- ' blockedWorkbooks: blockedWorkbooks.map((entry) => entry.name),',
3376
- ' validPriceRecords: workbookResults.reduce((sum, entry) => sum + entry.validPriceRecords, 0),',
3377
- ' errorPriceRecords: workbookResults.reduce((sum, entry) => sum + entry.errorPriceRecords, 0),',
3378
- ' insertPricesFromImportPayloadSize: allInsertPayloads.length,',
3379
- ' insertedCount,',
3380
- ' mongoDelta: { collection: "pricing-items", beforeBatchCount, afterBatchCount, beforeTotalCount, afterTotalCount, inserted: afterBatchCount - beforeBatchCount },',
3381
- ' screenshot: beforeAfterScreenshotPath,',
3382
- ' page: routeSummary',
3383
- ' };',
3384
- ' await renderPricingProofScreenshot(payload, caption).catch(() => undefined);',
3385
- ' writePricingProof(payload);',
3386
- ' const businessAssertion = {',
3387
- ' assertion: "Production pricing import validates every attached workbook, builds insertPricesFromImport payloads, and persists corresponding pricing-item records in local QA Mongo.",',
3388
- ' status: pass ? "pass" : "failed",',
3389
- ' result: pass ? "business_assertion_passed" : "business_assertion_failed",',
3390
- ' outcome: pass ? "business_assertion_passed" : "business_assertion_failed",',
3391
- ' workflow: "Upload each attached workbook through the production pricing import path or equivalent method path, record valid/error counts, insert payload size, and persisted pricing-items delta.",',
3392
- ' route: targetRoute,',
3393
- ' before: `Local QA pricing-items before proof batch ${proofBatchId}: ${beforeBatchCount}; total pricing-items before: ${beforeTotalCount}.`,',
3394
- ' action: `Parsed ${workbookResults.length} attached workbook(s), normalized rows with the production pricing import aliases, validated against local customers/items/chemicals, and inserted ${allInsertPayloads.length} QA-tagged pricing-item payload(s).`,',
3395
- ' expected: "Every attached workbook has non-zero validPriceRecords, non-zero insertPricesFromImport payload size, and matching local QA pricing-items insert delta.",',
3396
- ' after: `Local QA pricing-items after proof batch ${proofBatchId}: ${afterBatchCount}; total pricing-items after: ${afterTotalCount}; insertedCount=${insertedCount}.`,',
3397
- ' observed: caption,',
3398
- ' dataProof: workbookResults.map((entry) => `${entry.name}: totalRows=${entry.totalRows}, validPriceRecords=${entry.validPriceRecords}, errorPriceRecords=${entry.errorPriceRecords}, insertPricesFromImportPayloadSize=${entry.insertPricesFromImportPayloadSize}`).join("\\n"),',
3399
- ' mongoDelta: payload.mongoDelta,',
3400
- ' artifactPaths: [proofPath, beforeAfterScreenshotPath, matrixPath],',
3401
- ' metadata: { supportDiagnosisProof: pass, proofType: "pricing_import", methodPath: "server/src/methods/pricing.ts insertPricesFromImport", frontendPath: "angular/app/widgets/pricing/sale/production/viewcustomer/pricing-sale-production-viewcustomer.component.ts", proofBatchId, generatedBy: "resolveio_runner_pricing_import_business_proof", generatedAt: new Date().toISOString() }',
3402
- ' };',
3403
- ' writeJson(businessAssertionPath, businessAssertion);',
3404
- ' const matrix = readJson(matrixPath) || { status: "started", rows: [] };',
3405
- ' const rows = matrixRows(matrix);',
3406
- ' const rowIndex = rows.findIndex((candidate) => !/^(pass|passed)$/i.test(String(candidate && candidate.status || "")));',
3407
- ' const activeIndex = rowIndex >= 0 ? rowIndex : 0;',
3408
- ' if (rows[activeIndex]) {',
3409
- ' rows[activeIndex].status = pass ? "pass" : "failed";',
3410
- ' rows[activeIndex].result = pass ? "pass" : "failed";',
3411
- ' rows[activeIndex].acceptance_blocked = !pass;',
3412
- ' rows[activeIndex].screenshot = beforeAfterScreenshotPath;',
3413
- ' rows[activeIndex].screenshots = [beforeAfterScreenshotPath, passScreenshotPath].filter(Boolean);',
3414
- ' rows[activeIndex].caption = caption;',
3415
- ' rows[activeIndex].artifact = proofPath;',
3416
- ' rows[activeIndex].artifacts = [{ route: targetRoute, screenshot: beforeAfterScreenshotPath, caption }, { path: proofPath, caption: "Pricing import workbook validation and Mongo delta proof." }, { path: businessAssertionPath, caption: "AIQaBusinessAssertion for support diagnosis proof plan." }];',
3417
- ' rows[activeIndex].persisted_assertion = `Local QA Mongo pricing-items delta=${payload.mongoDelta.inserted}; payloadSize=${allInsertPayloads.length}; insertedCount=${insertedCount}.`;',
3418
- ' if (!pass) rows[activeIndex].blocker = caption;',
3419
- ' }',
3420
- ' matrix.rows = rows;',
3421
- ' matrix.status = pass ? "pass" : "failed";',
3422
- ' matrix.pricing_import_proof = proofPath;',
3423
- ' matrix.aiqa_business_assertion = businessAssertionPath;',
3424
- ' matrix.updated_at = new Date().toISOString();',
3425
- ' writeJson(matrixPath, matrix);',
3426
- ' return payload;',
3427
- ' } finally {',
3428
- ' await client.close().catch(() => undefined);',
3429
- ' }',
3430
- ' } catch (error) {',
3431
- ' const message = error && (error.stack || error.message) || String(error);',
3432
- ' const payload = writePricingProof({ status: "failed", targetRoute, proofBatchId, startedAt, updated_at: new Date().toISOString(), error: message });',
3433
- ' writeJson(businessAssertionPath, { assertion: "Production pricing import workbook proof", status: "failed", result: "business_assertion_failed", outcome: "business_assertion_failed", workflow: "Pricing import business proof", route: targetRoute, before: "", action: "Attempted deterministic pricing import proof.", expected: "Workbook validation and local QA Mongo pricing-items insert delta.", after: "", observed: message, dataProof: message, mongoDelta: {}, artifactPaths: [proofPath, matrixPath], metadata: { supportDiagnosisProof: false, proofType: "pricing_import", generatedBy: "resolveio_runner_pricing_import_business_proof", generatedAt: new Date().toISOString() } });',
3434
- ' const matrix = readJson(matrixPath) || { status: "started", rows: [] };',
3435
- ' const rows = matrixRows(matrix);',
3436
- ' if (rows[0]) { rows[0].status = "failed"; rows[0].result = "failed"; rows[0].acceptance_blocked = true; rows[0].blocker = message; rows[0].artifact = proofPath; }',
3437
- ' matrix.rows = rows;',
3438
- ' matrix.status = "failed";',
3439
- ' matrix.pricing_import_proof = proofPath;',
3440
- ' matrix.aiqa_business_assertion = businessAssertionPath;',
3441
- ' matrix.updated_at = new Date().toISOString();',
3442
- ' writeJson(matrixPath, matrix);',
3443
- ' return payload;',
3444
- ' }',
3445
- '}',
3446
- 'async function runAssetLocationRowProof(page, routeSummary) {',
3447
- ' const rowText = activeRowText();',
3448
- ' const context = readSeedAssetContext();',
3449
- ' if (!context || !/asset|unit|yard|location|current\\s+location/i.test(rowText)) return null;',
3450
- ' const proofPath = path.join(artifactDir, "qa-asset-location-proof.json");',
3451
- ' const staleTerms = staleLocationTerms(rowText);',
3452
- ' const data = await fetchAssetLocationProofData(context);',
3453
- ' const expectedNumbers = uniqueStrings(context.asset_numbers).filter((value) => /^\\d+[A-Za-z-]*$/.test(value));',
3454
- ' const missingNumbers = expectedNumbers.filter((number) => !data.rows.some((row) => row.number === number));',
3455
- ' const mismatchedRows = data.rows.filter((row) => !row.pass);',
3456
- ' if (expectedNumbers.length) {',
3457
- ' await page.goto(new URL(targetRoute, clientUrl).href, { waitUntil: "domcontentloaded", timeout: 60000 }).catch(() => undefined);',
3458
- ' await page.waitForFunction((numbers) => {',
3459
- ' const text = document.body ? document.body.innerText || "" : "";',
3460
- ' return numbers.every((number) => new RegExp(`\\\\b${String(number).replace(/[.*+?^${}()|[\\\\]\\\\\\\\]/g, "\\\\\\\\$&")}\\\\b`).test(text));',
3461
- ' }, { timeout: Number(process.env.RESOLVEIO_SUPPORT_QA_ROW_WAIT_MS || 15000) }, expectedNumbers).catch(() => undefined);',
3462
- ' routeSummary = await pageSummary(page);',
3463
- ' await page.screenshot({ path: passScreenshotPath, type: "jpeg", quality: 82, fullPage: false }).catch(() => undefined);',
3464
- ' }',
3465
- ' const listText = String((routeSummary && routeSummary.bodyTextSnippet) || "");',
3466
- ' const listMissing = expectedNumbers.filter((number) => !new RegExp(`\\\\b${number.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&")}\\\\b`).test(listText));',
3467
- ' const staleSeen = staleTerms.filter((term) => new RegExp(term.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&"), "i").test(listText) || data.rows.some((row) => new RegExp(term.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&"), "i").test([row.currentLocationName, row.defaultYardName].join(" "))));',
3468
- ' const artifacts = [];',
3469
- ' if (routeSummary) artifacts.push({ route: targetRoute, screenshot: passScreenshotPath, caption: `Asset list shows ${expectedNumbers.join(", ")} with canonical yard names and no stale yard labels.` });',
3470
- ' for (const row of data.rows) {',
3471
- ' const requiredTerms = [row.number || row.id, row.canonicalYardName || row.currentLocationName].filter(Boolean);',
3472
- ' const detail = await gotoAndSummarize(page, `/asset/detail/${row.id}`, `asset-location-detail-${row.number || row.id}.jpg`, requiredTerms);',
3473
- ' const edit = await gotoAndSummarize(page, `/asset/edit/${row.id}`, `asset-location-edit-${row.number || row.id}.jpg`, requiredTerms);',
3474
- ' const detailBody = String(detail.summary.bodyTextSnippet || "");',
3475
- ' const editBody = String(edit.summary.bodyTextSnippet || "");',
3476
- ' const detailHasProof = requiredTerms.every((term) => new RegExp(String(term).replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&"), "i").test(detailBody)) && !isShellOnlySummary(detail.summary);',
3477
- ' const editHasProof = requiredTerms.every((term) => new RegExp(String(term).replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&"), "i").test(editBody)) && !isShellOnlySummary(edit.summary);',
3478
- ' if (detailHasProof) artifacts.push({ route: detail.route, screenshot: detail.screenshot, caption: `Asset ${row.number || row.id} detail shows canonical yard ${row.canonicalYardName || row.currentLocationName}.`, bodyTextSnippet: detail.summary.bodyTextSnippet });',
3479
- ' if (editHasProof) artifacts.push({ route: edit.route, screenshot: edit.screenshot, caption: `Asset ${row.number || row.id} edit screen opens with canonical yard ${row.canonicalYardName || row.currentLocationName} selected/visible.`, bodyTextSnippet: edit.summary.bodyTextSnippet });',
3480
- ' if (!detailHasProof || !editHasProof) mismatchedRows.push({ ...row, proofMissing: `${!detailHasProof ? "detail" : ""}${!detailHasProof && !editHasProof ? "," : ""}${!editHasProof ? "edit" : ""}` });',
3481
- ' }',
3482
- ' const pass = !missingNumbers.length && !mismatchedRows.length && !listMissing.length && !staleSeen.length && data.rows.length >= Math.max(expectedNumbers.length, 1);',
3483
- ' const payload = { status: pass ? "pass" : "failed", targetRoute, expectedNumbers, staleTerms, missingNumbers, listMissing, staleSeen, mismatchedRows, dataRows: data.rows, artifacts, updated_at: new Date().toISOString() };',
3484
- ' writeJson(proofPath, payload);',
3485
- ' const matrix = readJson(matrixPath) || { status: "started", rows: [] };',
3486
- ' const rows = matrixRows(matrix);',
3487
- ' const rowIndex = rows.findIndex((candidate) => !/^(pass|passed)$/i.test(String(candidate && candidate.status || "")));',
3488
- ' const activeIndex = rowIndex >= 0 ? rowIndex : 0;',
3489
- ' if (rows[activeIndex]) {',
3490
- ' rows[activeIndex].status = pass ? "pass" : "failed";',
3491
- ' rows[activeIndex].result = pass ? "pass" : "failed";',
3492
- ' rows[activeIndex].screenshot = artifacts[0] && artifacts[0].screenshot || passScreenshotPath;',
3493
- ' rows[activeIndex].screenshots = artifacts.map((artifact) => artifact.screenshot).filter(Boolean);',
3494
- ' rows[activeIndex].caption = pass ? `Verified exact assets ${expectedNumbers.join(", ")} use canonical yard names ${uniqueStrings(data.rows.map((row) => row.canonicalYardName)).join(", ")} on list/detail/edit screens; stale names ${staleTerms.join(", ") || "from the ticket"} are absent.` : `Asset location QA failed: missing=${missingNumbers.join(",") || "none"} listMissing=${listMissing.join(",") || "none"} staleSeen=${staleSeen.join(",") || "none"} mismatched=${mismatchedRows.map((row) => row.number).join(",") || "none"}.`;',
3495
- ' rows[activeIndex].artifact = proofPath;',
3496
- ' rows[activeIndex].artifacts = artifacts;',
3497
- ' rows[activeIndex].persisted_assertion = `Local QA Mongo joined ${data.rows.length} asset(s) to yards; ${data.rows.map((row) => `${row.number}:${row.currentLocationName}->${row.canonicalYardName}`).join("; ")}`;',
3498
- ' if (!pass) rows[activeIndex].blocker = rows[activeIndex].caption;',
3499
- ' }',
3500
- ' matrix.rows = rows;',
3501
- ' matrix.status = pass ? "pass" : "failed";',
3502
- ' matrix.asset_location_proof = proofPath;',
3503
- ' matrix.updated_at = new Date().toISOString();',
3504
- ' writeJson(matrixPath, matrix);',
3505
- ' return payload;',
3506
- '}'
3507
- ], __read(buildResolveIOPartInventorySnapshotProofScriptLines()), false), [
3508
- 'function updateMatrix(status, screenshotPath, caption, assertion) {',
3509
- ' const matrix = readJson(matrixPath) || { status: "started", rows: [] };',
3510
- ' const routeProbePassed = status === "route_probe_pass" || status === "pass";',
3511
- ' const matrixStatus = status === "route_probe_pass" ? "in_progress" : status;',
3512
- ' matrix.workflow_probe = { status, outcome: status === "route_probe_pass" ? "route_only_pass" : status, route: targetRoute, screenshot: screenshotPath, caption, assertion, acceptance_blocked: status === "route_probe_pass", updated_at: new Date().toISOString() };',
3513
- ' matrix.updated_at = new Date().toISOString();',
3514
- ' const rows = Array.isArray(matrix.rows) ? matrix.rows : [];',
3515
- ' if (rows[0]) {',
3516
- ' rows[0].route_probe = { status: routeProbePassed ? "pass" : status, outcome: status === "route_probe_pass" ? "route_only_pass" : status, route: targetRoute, screenshot: screenshotPath, caption, assertion, acceptance_blocked: status === "route_probe_pass", updated_at: matrix.updated_at };',
3517
- ' if (!routeProbePassed) { rows[0].status = "blocked"; rows[0].screenshot = screenshotPath; rows[0].caption = caption; }',
3518
- ' else if (!rows[0].status || rows[0].status === "pending") { rows[0].status = "in_progress"; rows[0].result = "in_progress"; rows[0].acceptance_blocked = true; rows[0].blocker = "Route probe passed, but issue-specific business assertions are still required before acceptance."; }',
3519
- ' }',
3520
- ' matrix.rows = rows;',
3521
- ' if (!matrix.status || matrix.status === "started" || matrix.status === "pending") matrix.status = matrixStatus;',
3522
- ' writeJson(matrixPath, matrix);',
3523
- '}',
3524
- '',
3525
- '(async () => {',
3526
- ' fs.mkdirSync(artifactDir, { recursive: true });',
3527
- ' const puppeteer = requirePuppeteer();',
3528
- ' const browser = await launchBrowser(puppeteer);',
3529
- ' let page;',
3530
- ' try {',
3531
- ' await waitForHttpReady(clientUrl, "QA client");',
3532
- ' await waitForHttpReady(serverUrl, "QA server");',
3533
- ' page = await browser.newPage();',
3534
- ' await page.setViewport({ width: viewportWidth, height: viewportHeight });',
3535
- ' const auth = await login();',
3536
- ' await seedAuth(page, auth);',
3537
- ' await waitForAuthenticatedApp(page);',
3538
- ' await page.goto(`${clientUrl}${targetRoute}`, { waitUntil: "domcontentloaded", timeout: 60000 });',
3539
- ' await page.waitForSelector("body", { timeout: 30000 });',
3540
- ' let summary = await waitForHydratedTargetRoute(page);',
3541
- ' if (summary.hasLoginText || summary.hasOfflineModeText || !summary.bodyTextSnippet) throw new Error(`Workflow route did not reach authenticated app: ${JSON.stringify(summary).slice(0, 1000)}`);',
3542
- ' summary = await waitForBillingDashboardWork(page) || summary;',
3543
- ' const caption = `Workflow route ready: ${targetRoute} loaded in authenticated local QA with live seeded data available.`;',
3544
- ' await page.screenshot({ path: passScreenshotPath, type: "jpeg", quality: 82, fullPage: false });',
3545
- ' updateMatrix("route_probe_pass", passScreenshotPath, caption, "Authenticated customer workflow route loaded; deeper row-specific UI/data proof still required.");',
3546
- ' let specializedProof = await runPricingImportProof(page, summary);',
3547
- ' let specializedProofPath = specializedProof ? path.join(artifactDir, "qa-pricing-import-proof.json") : "";',
3548
- ' let specializedProofLabel = specializedProof ? "Pricing import workbook business proof" : "";',
3549
- ' if (!specializedProof) {',
3550
- ' specializedProof = await runPartInventorySnapshotMethodProof(page, summary, auth);',
3551
- ' specializedProofPath = specializedProof ? path.join(artifactDir, "qa-part-inventory-snapshot-proof.json") : "";',
3552
- ' specializedProofLabel = specializedProof ? "Part inventory snapshot report method business proof" : "";',
3553
- ' }',
3554
- ' if (!specializedProof) {',
3555
- ' specializedProof = await runAssetLocationRowProof(page, summary);',
3556
- ' specializedProofPath = specializedProof ? path.join(artifactDir, "qa-asset-location-proof.json") : "";',
3557
- ' specializedProofLabel = specializedProof ? "Asset location list/detail/edit business proof" : "";',
3558
- ' }',
3559
- ' if (specializedProof && specializedProof.status !== "pass") {',
3560
- ' const result = { status: "failed", clientUrl, serverUrl, targetRoute, screenshot: passScreenshotPath, caption: `${specializedProofLabel || "Specialized business proof"} failed; see ${path.basename(specializedProofPath)}.`, page: summary, matrix: matrixPath, specializedProof: specializedProofPath };',
3561
- ' writeJson(resultPath, result);',
3562
- ' console.error(JSON.stringify(result, null, 2));',
3563
- ' process.exitCode = 1;',
3564
- ' return;',
3565
- ' }',
3566
- ' const result = { status: specializedProof ? "pass" : "route_probe_pass", outcome: specializedProof ? "business_assertion_passed" : "route_only_pass", acceptance_blocked: !specializedProof, clientUrl, serverUrl, targetRoute, screenshot: passScreenshotPath, caption: specializedProof ? `${specializedProofLabel} passed with issue-specific data proof.` : caption, page: summary, matrix: matrixPath, specializedProof: specializedProofPath };',
3567
- ' writeJson(resultPath, result);',
3568
- ' console.log(JSON.stringify(result, null, 2));',
3569
- ' } catch (error) {',
3570
- ' let summary = null;',
3571
- ' try { if (page) { await page.screenshot({ path: failScreenshotPath, type: "jpeg", quality: 82, fullPage: false }); summary = await pageSummary(page); } } catch (screenshotError) {}',
3572
- ' if (summary && pathMatchesRoute(summary.url, targetRoute) && !summary.hasLoginText && !summary.hasOfflineModeText && !isShellOnlySummary(summary)) {',
3573
- ' const caption = `Workflow route ready after late hydration: ${targetRoute} loaded in authenticated local QA with live seeded data available.`;',
3574
- ' try { if (page) await page.screenshot({ path: passScreenshotPath, type: "jpeg", quality: 82, fullPage: false }); } catch (screenshotError) {}',
3575
- ' updateMatrix("route_probe_pass", passScreenshotPath, caption, "Authenticated customer workflow route loaded after transient offline/shell state; deeper row-specific UI/data proof still required.");',
3576
- ' const result = { status: "route_probe_pass", outcome: "route_only_pass", acceptance_blocked: true, clientUrl, serverUrl, targetRoute, screenshot: passScreenshotPath, caption, recoveredFromTransientError: error && (error.message || String(error)) || "late hydration", page: summary, matrix: matrixPath };',
3577
- ' writeJson(resultPath, result);',
3578
- ' console.log(JSON.stringify(result, null, 2));',
3579
- ' process.exitCode = 0;',
3580
- ' return;',
3581
- ' }',
3582
- ' const bootstrapProof = freshAuthBootstrapProof();',
3583
- ' if (bootstrapProof) {',
3584
- ' const caption = `Workflow route ready from authenticated bootstrap proof: ${targetRoute} loaded for the seeded QA user with live data before a transient probe shell state.`;',
3585
- ' updateMatrix("route_probe_pass", bootstrapProof.screenshot || passScreenshotPath, caption, "Authenticated bootstrap proof loaded the exact target route with live seeded data; deeper row-specific UI/data proof still required.");',
3586
- ' const result = { status: "route_probe_pass", outcome: "route_only_pass", acceptance_blocked: true, clientUrl, serverUrl, targetRoute, screenshot: bootstrapProof.screenshot || passScreenshotPath, caption, recoveredFromTransientError: error && (error.message || String(error)) || "bootstrap proof recovery", page: bootstrapProof.page, authBootstrapProof: authBootstrapResultPath, matrix: matrixPath };',
3587
- ' writeJson(resultPath, result);',
3588
- ' console.log(JSON.stringify(result, null, 2));',
3589
- ' process.exitCode = 0;',
3590
- ' return;',
3591
- ' }',
3592
- ' const caption = `Blocked before workflow QA: ${targetRoute} could not be reached in authenticated local QA.`;',
3593
- ' updateMatrix("blocked", failScreenshotPath, caption, error && (error.message || String(error)) || "Workflow probe failed");',
3594
- ' const result = { status: "blocked", clientUrl, serverUrl, targetRoute, screenshot: failScreenshotPath, caption, error: error && (error.stack || error.message) || String(error), page: summary, matrix: matrixPath };',
3595
- ' writeJson(resultPath, result);',
3596
- ' console.error(JSON.stringify(result, null, 2));',
3597
- ' process.exitCode = 1;',
3598
- ' } finally {',
3599
- ' await browser.close().catch(() => undefined);',
3600
- ' process.exit(process.exitCode || 0);',
3601
- ' }',
3602
- '})();',
1645
+ 'function updateMatrix(status, screenshotPath, caption, assertion) { const matrix = readJson(matrixPath) || { status: "started", rows: [] }; const routeProbePassed = status === "route_probe_pass" || status === "pass"; const matrixStatus = status === "route_probe_pass" ? "in_progress" : status; matrix.workflow_probe = { status, outcome: status === "route_probe_pass" ? "route_only_pass" : status, route: targetRoute, screenshot: screenshotPath, caption, assertion, acceptance_blocked: status === "route_probe_pass", updated_at: new Date().toISOString() }; matrix.updated_at = new Date().toISOString(); const rows = matrixRows(matrix); if (rows[0]) { rows[0].route_probe = { status: routeProbePassed ? "pass" : status, outcome: status === "route_probe_pass" ? "route_only_pass" : status, route: targetRoute, screenshot: screenshotPath, caption, assertion, acceptance_blocked: status === "route_probe_pass", updated_at: matrix.updated_at }; if (!routeProbePassed) { rows[0].status = "blocked"; rows[0].screenshot = screenshotPath; rows[0].caption = caption; } else if (!rows[0].status || rows[0].status === "pending") { rows[0].status = "in_progress"; rows[0].result = "in_progress"; rows[0].acceptance_blocked = true; rows[0].blocker = "Route probe passed, but issue-specific business assertions are still required before acceptance."; } } matrix.rows = rows; if (!matrix.status || matrix.status === "started" || matrix.status === "pending") matrix.status = matrixStatus; writeJson(matrixPath, matrix); }',
1646
+ '(async () => { fs.mkdirSync(artifactDir, { recursive: true }); const puppeteer = requirePuppeteer(); const browser = await launchBrowser(puppeteer); let page; try { await waitForHttpReady(clientUrl, "QA client"); await waitForHttpReady(serverUrl, "QA server"); page = await browser.newPage(); await page.setViewport({ width: viewportWidth, height: viewportHeight }); const auth = await login(); await seedAuth(page, auth); await waitForAuthenticatedApp(page); await page.goto(clientUrl + targetRoute, { waitUntil: "domcontentloaded", timeout: 60000 }); await page.waitForSelector("body", { timeout: 30000 }); let summary = await waitForHydratedTargetRoute(page); if (summary.hasLoginText || summary.hasOfflineModeText || !summary.bodyTextSnippet) throw new Error("Workflow route did not reach authenticated app: " + JSON.stringify(summary).slice(0, 1000)); summary = await waitForRouteSpecificBusinessState(page) || summary; const caption = "Workflow route ready: " + targetRoute + " loaded in authenticated local QA with live seeded data available."; await page.screenshot({ path: passScreenshotPath, type: "jpeg", quality: 82, fullPage: false }); updateMatrix("route_probe_pass", passScreenshotPath, caption, "Authenticated customer workflow route loaded; deeper row-specific UI/data proof still required."); const result = { status: "route_probe_pass", outcome: "route_only_pass", acceptance_blocked: true, clientUrl, serverUrl, targetRoute, screenshot: passScreenshotPath, caption, page: summary, matrix: matrixPath }; writeJson(resultPath, result); console.log(JSON.stringify(result, null, 2)); } catch (error) { let summary = null; try { if (page) { await page.screenshot({ path: failScreenshotPath, type: "jpeg", quality: 82, fullPage: false }); summary = await pageSummary(page); } } catch (screenshotError) {} if (summary && pathMatchesRoute(summary.url, targetRoute) && !summary.hasLoginText && !summary.hasOfflineModeText && !isShellOnlySummary(summary)) { const caption = "Workflow route ready after late hydration: " + targetRoute + " loaded in authenticated local QA with live seeded data available."; try { if (page) await page.screenshot({ path: passScreenshotPath, type: "jpeg", quality: 82, fullPage: false }); } catch (screenshotError) {} updateMatrix("route_probe_pass", passScreenshotPath, caption, "Authenticated customer workflow route loaded after transient offline/shell state; deeper row-specific UI/data proof still required."); const result = { status: "route_probe_pass", outcome: "route_only_pass", acceptance_blocked: true, clientUrl, serverUrl, targetRoute, screenshot: passScreenshotPath, caption, recoveredFromTransientError: error && (error.message || String(error)) || "late hydration", page: summary, matrix: matrixPath }; writeJson(resultPath, result); console.log(JSON.stringify(result, null, 2)); process.exitCode = 0; return; } const bootstrapProof = freshAuthBootstrapProof(); if (bootstrapProof) { const caption = "Workflow route ready from authenticated bootstrap proof: " + targetRoute + " loaded for the seeded QA user with live data before a transient probe shell state."; updateMatrix("route_probe_pass", bootstrapProof.screenshot || passScreenshotPath, caption, "Authenticated bootstrap proof loaded the exact target route with live seeded data; deeper row-specific UI/data proof still required."); const result = { status: "route_probe_pass", outcome: "route_only_pass", acceptance_blocked: true, clientUrl, serverUrl, targetRoute, screenshot: bootstrapProof.screenshot || passScreenshotPath, caption, recoveredFromTransientError: error && (error.message || String(error)) || "bootstrap proof recovery", page: bootstrapProof.page, authBootstrapProof: authBootstrapResultPath, matrix: matrixPath }; writeJson(resultPath, result); console.log(JSON.stringify(result, null, 2)); process.exitCode = 0; return; } const caption = "Blocked before workflow QA: " + targetRoute + " could not be reached in authenticated local QA."; updateMatrix("blocked", failScreenshotPath, caption, error && (error.message || String(error)) || "Workflow probe failed"); const result = { status: "blocked", clientUrl, serverUrl, targetRoute, screenshot: failScreenshotPath, caption, error: error && (error.stack || error.message) || String(error), page: summary, matrix: matrixPath }; writeJson(resultPath, result); console.error(JSON.stringify(result, null, 2)); process.exitCode = 1; } finally { await browser.close().catch(() => undefined); process.exit(process.exitCode || 0); } })();',
3603
1647
  ''
3604
- ], false).join('\n');
1648
+ ].join('\n');
3605
1649
  }
3606
1650
  function buildResolveIORunnerQaToolsReadme(options) {
3607
1651
  if (options === void 0) { options = {}; }
@@ -3637,15 +1681,11 @@ function buildResolveIORunnerQaToolsReadme(options) {
3637
1681
  'Do not use version-fragile browser helpers such as `page.$x` or `page.waitForTimeout`; use DOM traversal/locator-compatible helpers and a local `delay(ms)` promise helper, then rerun the same matrix row if the QA script itself fails.',
3638
1682
  'Do not run `npm run build-dev`, `ng build`, or another Angular compile while keepalive `ng serve` is running. If a full Angular build is required after browser QA, first run the staged `stop-local-qa.sh`, then build, then restart `run-local-qa.sh` for final browser proof.',
3639
1683
  'Use desktop screenshots at 1920x1080 by default unless the task is explicitly mobile/responsive. Every screenshot must have a customer-facing caption.',
3640
- 'For From/To, source/target, dropdown, combobox, rio-select, filter, item, customer, yard, chemical, or treatment-plan selection workflows, capture pass screenshots only after the selected values are visible in the controls/chips/labels. The QA matrix data/assertion/caption must name the selected values; empty controls, placeholder text, or route-load screenshots are blocker evidence, not pass evidence.',
3641
- 'For import/export/form-submit/data workflows, prove before/action/after with representative data and a concrete row/count/value assertion.',
3642
- 'For pricing import/upload bugs, drive the actual Manage Pricing browser import for the attached/template variants and assert localhost Mongo pricing row/count/value changes. Validator-only proof must fail.',
3643
- 'For Part Inventory Snapshot / reportDailyInventorySnapshot warehouse bugs, use the deterministic method proof in `qa-workflow-probe.js`: it calls the local websocket method for the seeded day/Parts section, compares returned `warehouse_location` values against seeded yard lineage, and writes `aiqa-business-assertion.json` plus `support-business-proof-before-after.png` without launching another model or Angular build.',
3644
- 'For asset location/current-yard bugs, prove the exact asset/unit on list, detail, and edit/save screens, with a joined/canonical yard assertion and stale yard name absence proof.',
3645
- 'For Update Interchangeables/Mass Update chemical bugs, select the real source and target chemicals, run the update, and assert before/after treatment-plan/tank document counts or ids in localhost Mongo. Route-load or dropdown-only proof must fail.',
3646
- 'For data-backed workflows, run `qa-live-data-seed.js` after the local app is ready and before browser clickthrough. It reads a bounded live-data slice from `RESOLVEIO_QA_LIVE_MONGO_URL` or staged mongo context, writes only to localhost QA Mongo, prefers ticket-mentioned invoice/BOL/order/ticket identifiers, and records `qa-artifacts/qa-live-data-seed-result.json`.',
1684
+ 'For selection workflows, capture pass screenshots only after the selected values are visible in the controls/chips/labels or after the resulting data state is visible. The QA matrix data/assertion/caption must name the selected values; empty controls, placeholder text, or route-load screenshots are blocker evidence, not pass evidence.',
1685
+ 'Let the model choose the proof technique from the ticket, diagnosis, code, UI, and local data evidence. Shared runner tools must not prescribe product-specific routes, buttons, collections, workflows, or proof categories. The only shared acceptance rule is before/action/after evidence for the reported behavior; route-load, compile-pass, scorecard-pass, or model-claim alone is not success.',
1686
+ 'For data-backed workflows, run `qa-live-data-seed.js` after the local app is ready and before browser clickthrough. It reads a bounded live-data slice from `RESOLVEIO_QA_LIVE_MONGO_URL` or staged mongo context, writes only to localhost QA Mongo, discovers candidate collections from the ticket/proof text instead of product-specific names, and records `qa-artifacts/qa-live-data-seed-result.json`.',
3647
1687
  'For support-ticket QA, use the ticket reporter or named affected user when the live seed can identify/copy that user. `qa-live-data-seed-result.json.selected.qa_user_context` and `auth-bootstrap-result.json.user` must agree, or the row is not ready to pass.',
3648
- 'For customer-reported data bugs, QA must seed and prove the exact named production records in local QA. If the exact unit/BOL/invoice/chemical/customer/yard/user is missing, fail with a seed/query blocker instead of switching to a representative record.',
1688
+ 'If named production data is unavailable, record `seed_data_unavailable` and let the model choose the narrowest fallback proof strategy: fixture data, method-level proof, DOM-only proof with explanation, or a blocker when the current proof plan explicitly requires that exact record.',
3649
1689
  'For bug fixes, use `bugfix-comparison-qa.sh` so baseline/master runs the exact same repro before the candidate/PR run. A passing candidate is not enough unless the comparison result shows baseline failed or the report explicitly explains why the baseline failure could not be reproduced.',
3650
1690
  "Use `$".concat(usernameVar, "` and `$").concat(passwordVar, "` for the local fixture admin account unless ticket/app-specific credentials are provided."),
3651
1691
  'The env file reuses `/var/lib/resolveio/puppeteer`, npm cache, worker-safe Browserslist settings, and Angular cache prep so QA should not download a browser or rebuild cold caches unnecessarily.',
@@ -3655,6 +1695,5 @@ function buildResolveIORunnerQaToolsReadme(options) {
3655
1695
  ''
3656
1696
  ].filter(function (line) { return line !== ''; }).join('\n');
3657
1697
  }
3658
- var templateObject_1;
3659
1698
 
3660
1699
  //# sourceMappingURL=ai-runner-qa-tools.js.map