@resolveio/server-lib 22.3.93 → 22.3.95

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.
@@ -30,6 +30,8 @@ function buildResolveIORunnerQaEnvScript(options) {
30
30
  var jobId = shellDoubleQuote(options.jobId || '');
31
31
  var ownerId = shellDoubleQuote(options.ownerId || '');
32
32
  var runnerToken = shellDoubleQuote(options.runnerToken || '');
33
+ var runnerRunId = shellDoubleQuote(options.runnerRunId || '');
34
+ var runnerGeneration = shellDoubleQuote(String(options.runnerGeneration || ''));
33
35
  var mongoRuntimePath = shellDoubleQuote(options.mongoRuntimePath || '');
34
36
  var qaLiveDataRequired = options.qaLiveDataRequired === true ? 'true' : 'false';
35
37
  var toolsBinPath = options.toolsBinPath || '';
@@ -67,6 +69,19 @@ function buildResolveIORunnerQaEnvScript(options) {
67
69
  "export ".concat(mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_HOME' : 'RESOLVEIO_RUNNER_QA_HOME', "=\"").concat('${' + (mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_HOME' : 'RESOLVEIO_RUNNER_QA_HOME') + ':-' + homeRoot + '}', "\""),
68
70
  "RESOLVEIO_QA_TMP=\"".concat('${' + (mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_TMP' : 'RESOLVEIO_RUNNER_QA_TMP') + '}', "\""),
69
71
  "RESOLVEIO_QA_HOME=\"".concat('${' + (mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_HOME' : 'RESOLVEIO_RUNNER_QA_HOME') + '}', "\""),
72
+ 'RESOLVEIO_QA_TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"',
73
+ 'if [ -z "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" ] && [ -f "$RESOLVEIO_QA_TOOLS_DIR/current-codex-lane" ]; then',
74
+ ' RESOLVEIO_SUPPORT_CODEX_LANE="$(tr -d "\\r\\n\\t " < "$RESOLVEIO_QA_TOOLS_DIR/current-codex-lane" 2>/dev/null || true)"',
75
+ ' export RESOLVEIO_SUPPORT_CODEX_LANE',
76
+ 'fi',
77
+ 'resolveio_support_codex_lane_is_build() {',
78
+ ' case "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" in build|support_build|support-build) return 0 ;; *) return 1 ;; esac',
79
+ '}',
80
+ 'resolveio_support_codex_block_build_lane_qa() {',
81
+ ' resolveio_support_codex_lane_is_build || return 0',
82
+ ' 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',
83
+ ' return 86',
84
+ '}',
70
85
  'mkdir -p "$RESOLVEIO_QA_HOME/.nvm" "$RESOLVEIO_QA_TMP/npm-cache" "$RESOLVEIO_QA_TMP/mongodb-binaries" "$RESOLVEIO_QA_TMP/mongodb-memory-server-core"',
71
86
  'if [ ! -f "$RESOLVEIO_QA_HOME/.nvm/nvm.sh" ]; then',
72
87
  ' cat > "$RESOLVEIO_QA_HOME/.nvm/nvm.sh" <<\'RESOLVEIO_NVM_SHIM\'',
@@ -81,6 +96,129 @@ function buildResolveIORunnerQaEnvScript(options) {
81
96
  'fi',
82
97
  'export HOME="$RESOLVEIO_QA_HOME"',
83
98
  'export NVM_DIR="$RESOLVEIO_QA_HOME/.nvm"',
99
+ 'export RESOLVEIO_QA_COMMAND_GUARD_BIN="$RESOLVEIO_QA_TMP/command-guard-bin"',
100
+ 'mkdir -p "$RESOLVEIO_QA_COMMAND_GUARD_BIN"',
101
+ 'resolveio_qa_find_real_command() {',
102
+ ' local name="$1"',
103
+ ' local old_ifs="$IFS"',
104
+ ' IFS=":"',
105
+ ' for dir in $PATH; do',
106
+ ' [ -n "$dir" ] || continue',
107
+ ' [ "$dir" = "$RESOLVEIO_QA_COMMAND_GUARD_BIN" ] && continue',
108
+ ' if [ -x "$dir/$name" ]; then',
109
+ ' echo "$dir/$name"',
110
+ ' IFS="$old_ifs"',
111
+ ' return 0',
112
+ ' fi',
113
+ ' done',
114
+ ' IFS="$old_ifs"',
115
+ ' return 1',
116
+ '}',
117
+ 'export RESOLVEIO_QA_REAL_NPM="${RESOLVEIO_QA_REAL_NPM:-$(resolveio_qa_find_real_command npm || true)}"',
118
+ 'if [ -n "$RESOLVEIO_QA_REAL_NPM" ]; then',
119
+ ' cat > "$RESOLVEIO_QA_COMMAND_GUARD_BIN/npm" <<\'RESOLVEIO_QA_NPM_GUARD\'',
120
+ '#!/usr/bin/env bash',
121
+ 'set -u',
122
+ 'REAL_NPM="${RESOLVEIO_QA_REAL_NPM:-}"',
123
+ 'GUARD_BIN="${RESOLVEIO_QA_COMMAND_GUARD_BIN:-}"',
124
+ 'if [ -z "$REAL_NPM" ] || [ "$REAL_NPM" = "$0" ]; then',
125
+ ' OLD_IFS="$IFS"',
126
+ ' IFS=":"',
127
+ ' for dir in $PATH; do',
128
+ ' [ -n "$dir" ] || continue',
129
+ ' [ -n "$GUARD_BIN" ] && [ "$dir" = "$GUARD_BIN" ] && continue',
130
+ ' if [ -x "$dir/npm" ]; then REAL_NPM="$dir/npm"; break; fi',
131
+ ' done',
132
+ ' IFS="$OLD_IFS"',
133
+ 'fi',
134
+ 'if [ -z "$REAL_NPM" ] || [ ! -x "$REAL_NPM" ]; then',
135
+ ' echo "ResolveIO QA command guard: unable to locate real npm." >&2',
136
+ ' exit 127',
137
+ 'fi',
138
+ 'if [ "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" = "build" ] || [ "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" = "support_build" ] || [ "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" = "support-build" ]; then',
139
+ ' if [ "${1:-}" = "run" ]; then',
140
+ ' case "${2:-}" in',
141
+ ' server|client|start|dev|serve|local-qa|qa|qa:*|test:browser|test:e2e|e2e)',
142
+ ' echo "ResolveIO support lane guard: refusing npm run ${2:-} from the 5.3 build lane. Browser/server/client QA belongs to the 5.4 mini QA lane." >&2',
143
+ ' exit 86',
144
+ ' ;;',
145
+ ' esac',
146
+ ' fi',
147
+ 'fi',
148
+ 'is_angular_build=0',
149
+ 'if [ "${1:-}" = "run" ]; then',
150
+ ' case "${2:-}" in',
151
+ ' build|build-dev|build-prod) is_angular_build=1 ;;',
152
+ ' esac',
153
+ 'fi',
154
+ 'resolveio_lock_live() {',
155
+ ' local file="$1"',
156
+ ' local ttl="${RESOLVEIO_QA_COMMAND_LOCK_TTL_SECONDS:-1800}"',
157
+ ' [ -f "$file" ] || return 1',
158
+ ' node - "$file" "$ttl" <<\'RESOLVEIO_QA_LOCK_LIVE\'',
159
+ 'const fs = require("fs");',
160
+ 'const [file, ttlRaw] = process.argv.slice(2);',
161
+ 'const ttlMs = Math.max(1, Number(ttlRaw || 1800)) * 1000;',
162
+ 'try {',
163
+ ' const data = JSON.parse(fs.readFileSync(file, "utf8"));',
164
+ ' const pid = Number(data.pid || 0);',
165
+ ' const heartbeat = Date.parse(data.heartbeat_at || data.started_at || "");',
166
+ ' if (!pid || !heartbeat || Date.now() - heartbeat > ttlMs) process.exit(1);',
167
+ ' process.kill(pid, 0);',
168
+ ' process.exit(0);',
169
+ '} catch (error) { process.exit(1); }',
170
+ 'RESOLVEIO_QA_LOCK_LIVE',
171
+ '}',
172
+ 'if [ "$is_angular_build" = "1" ]; then',
173
+ ' PROJECT_ROOT="${RESOLVEIO_SUPPORT_QA_PROJECT_ROOT:-${RESOLVEIO_RUNNER_QA_PROJECT_ROOT:-$(pwd)}}"',
174
+ ' PROJECT_ROOT="$(cd "$PROJECT_ROOT" 2>/dev/null && pwd || pwd)"',
175
+ ' ARTIFACT_DIR="$PROJECT_ROOT/qa-artifacts"',
176
+ ' mkdir -p "$ARTIFACT_DIR/.command-locks"',
177
+ ' QA_LOCK="$ARTIFACT_DIR/.qa.lock/lock.json"',
178
+ ' if resolveio_lock_live "$QA_LOCK"; then',
179
+ ' echo "ResolveIO QA command guard: refusing npm run ${2:-} while local QA harness is active for $PROJECT_ROOT. Stop QA first with stop-local-qa.sh." >&2',
180
+ ' exit 86',
181
+ ' fi',
182
+ ' BUILD_LOCK_DIR="$ARTIFACT_DIR/.command-locks/angular-build.lock"',
183
+ ' BUILD_LOCK_JSON="$BUILD_LOCK_DIR/lock.json"',
184
+ ' if ! mkdir "$BUILD_LOCK_DIR" 2>/dev/null; then',
185
+ ' if resolveio_lock_live "$BUILD_LOCK_JSON"; then',
186
+ ' echo "ResolveIO QA command guard: refusing duplicate npm run ${2:-}; another Angular build is active for $PROJECT_ROOT." >&2',
187
+ ' exit 86',
188
+ ' fi',
189
+ ' rm -rf "$BUILD_LOCK_DIR" 2>/dev/null || true',
190
+ ' if ! mkdir "$BUILD_LOCK_DIR" 2>/dev/null; then',
191
+ ' echo "ResolveIO QA command guard: unable to acquire Angular build lock for $PROJECT_ROOT." >&2',
192
+ ' exit 86',
193
+ ' fi',
194
+ ' fi',
195
+ ' cat > "$BUILD_LOCK_JSON" <<EOF',
196
+ '{"pid": $$, "command": "npm $*", "project_root": "$PROJECT_ROOT", "started_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "heartbeat_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"}',
197
+ 'EOF',
198
+ ' ( while true; do node - "$BUILD_LOCK_JSON" <<\'RESOLVEIO_QA_BUILD_HEARTBEAT\'',
199
+ 'const fs = require("fs");',
200
+ 'const [file] = process.argv.slice(2);',
201
+ 'try {',
202
+ ' const data = JSON.parse(fs.readFileSync(file, "utf8"));',
203
+ ' data.heartbeat_at = new Date().toISOString();',
204
+ ' fs.writeFileSync(file, JSON.stringify(data, null, 2));',
205
+ '} catch (error) {}',
206
+ 'RESOLVEIO_QA_BUILD_HEARTBEAT',
207
+ ' sleep 10',
208
+ ' done ) >/dev/null 2>&1 &',
209
+ ' HEARTBEAT_PID="$!"',
210
+ ' cleanup_build_lock() {',
211
+ ' kill "$HEARTBEAT_PID" >/dev/null 2>&1 || true',
212
+ ' rm -rf "$BUILD_LOCK_DIR" >/dev/null 2>&1 || true',
213
+ ' }',
214
+ ' trap cleanup_build_lock EXIT INT TERM',
215
+ 'fi',
216
+ 'exec "$REAL_NPM" "$@"',
217
+ 'RESOLVEIO_QA_NPM_GUARD',
218
+ ' chmod +x "$RESOLVEIO_QA_COMMAND_GUARD_BIN/npm"',
219
+ ' export PATH="$RESOLVEIO_QA_COMMAND_GUARD_BIN:$PATH"',
220
+ ' hash -r 2>/dev/null || true',
221
+ 'fi',
84
222
  toolsBinPath ? "export PATH=\"".concat(toolsBinPath, ":$PATH\"") : '',
85
223
  'export NODE_ENV=development',
86
224
  '# Local QA must start the application HTTP server even when launched from a dedicated support-manager worker.',
@@ -125,6 +263,10 @@ function buildResolveIORunnerQaEnvScript(options) {
125
263
  "export ".concat(envVar(altMode, 'OWNER_ID'), "=\"").concat('${' + envVar(altMode, 'OWNER_ID') + ':-${' + envVar(mode, 'OWNER_ID') + '}}', "\""),
126
264
  "export ".concat(envVar(mode, 'RUNNER_TOKEN'), "=\"").concat('${' + envVar(mode, 'RUNNER_TOKEN') + ':-${' + envVar(altMode, 'RUNNER_TOKEN') + ':-' + runnerToken + '}}', "\""),
127
265
  "export ".concat(envVar(altMode, 'RUNNER_TOKEN'), "=\"").concat('${' + envVar(altMode, 'RUNNER_TOKEN') + ':-${' + envVar(mode, 'RUNNER_TOKEN') + '}}', "\""),
266
+ "export ".concat(envVar(mode, 'RUNNER_RUN_ID'), "=\"").concat('${' + envVar(mode, 'RUNNER_RUN_ID') + ':-${' + envVar(altMode, 'RUNNER_RUN_ID') + ':-' + runnerRunId + '}}', "\""),
267
+ "export ".concat(envVar(altMode, 'RUNNER_RUN_ID'), "=\"").concat('${' + envVar(altMode, 'RUNNER_RUN_ID') + ':-${' + envVar(mode, 'RUNNER_RUN_ID') + '}}', "\""),
268
+ "export ".concat(envVar(mode, 'RUNNER_GENERATION'), "=\"").concat('${' + envVar(mode, 'RUNNER_GENERATION') + ':-${' + envVar(altMode, 'RUNNER_GENERATION') + ':-' + runnerGeneration + '}}', "\""),
269
+ "export ".concat(envVar(altMode, 'RUNNER_GENERATION'), "=\"").concat('${' + envVar(altMode, 'RUNNER_GENERATION') + ':-${' + envVar(mode, 'RUNNER_GENERATION') + '}}', "\""),
128
270
  mongoRuntimePath ? "export ".concat(envVar(mode, 'MONGO_RUNTIME_PATH'), "=\"").concat('${' + envVar(mode, 'MONGO_RUNTIME_PATH') + ':-${' + envVar(altMode, 'MONGO_RUNTIME_PATH') + ':-' + mongoRuntimePath + '}}', "\"") : '',
129
271
  mongoRuntimePath ? "export ".concat(envVar(altMode, 'MONGO_RUNTIME_PATH'), "=\"").concat('${' + envVar(altMode, 'MONGO_RUNTIME_PATH') + ':-${' + envVar(mode, 'MONGO_RUNTIME_PATH') + '}}', "\"") : '',
130
272
  mongoRuntimePath ? "export RESOLVEIO_QA_MONGO_RUNTIME_PATH=\"".concat('${RESOLVEIO_QA_MONGO_RUNTIME_PATH:-$' + envVar(mode, 'MONGO_RUNTIME_PATH') + '}', "\"") : '',
@@ -168,6 +310,10 @@ function buildResolveIORunnerLocalQaScript() {
168
310
  'set -u',
169
311
  'TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"',
170
312
  'source "$TOOLS_DIR/env.sh"',
313
+ 'if resolveio_support_codex_lane_is_build 2>/dev/null; then',
314
+ ' resolveio_support_codex_block_build_lane_qa',
315
+ ' exit 86',
316
+ 'fi',
171
317
  'PROJECT_ROOT="${1:-$(pwd)}"',
172
318
  'PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd)"',
173
319
  'ARTIFACT_DIR="$PROJECT_ROOT/qa-artifacts"',
@@ -442,6 +588,134 @@ function buildResolveIORunnerLocalQaScript() {
442
588
  ' done',
443
589
  ' return 1',
444
590
  '}',
591
+ 'mongo_tcp_ready() {',
592
+ ' node - "$MONGO_PORT" <<\'RESOLVEIO_QA_MONGO_TCP\'',
593
+ 'const net = require("net");',
594
+ 'const port = Number(process.argv[2] || 3001);',
595
+ 'const socket = net.createConnection({ host: "127.0.0.1", port, timeout: 1200 });',
596
+ 'socket.once("connect", () => { socket.destroy(); process.exit(0); });',
597
+ 'socket.once("timeout", () => { socket.destroy(); process.exit(1); });',
598
+ 'socket.once("error", () => process.exit(1));',
599
+ 'RESOLVEIO_QA_MONGO_TCP',
600
+ '}',
601
+ 'start_local_mongo_for_prestart_seed() {',
602
+ ' if mongo_tcp_ready >/dev/null 2>&1; then return 0; fi',
603
+ ' if command -v mongod >/dev/null 2>&1; then',
604
+ ' mkdir -p "$PROJECT_ROOT/server/mongo/data/db" "$PROJECT_ROOT/server/mongo/log"',
605
+ ' mongod --fork --dbpath "$PROJECT_ROOT/server/mongo/data/db" --logpath "$PROJECT_ROOT/server/mongo/log/mongo.log" --port "$MONGO_PORT" --replSet rs0 --bind_ip 127.0.0.1 >/dev/null 2>&1 || true',
606
+ ' fi',
607
+ ' local end=$((SECONDS + 45))',
608
+ ' while [ "$SECONDS" -lt "$end" ]; do',
609
+ ' if mongo_tcp_ready >/dev/null 2>&1; then break; fi',
610
+ ' sleep 2',
611
+ ' done',
612
+ ' if ! mongo_tcp_ready >/dev/null 2>&1; then return 1; fi',
613
+ ' if command -v mongosh >/dev/null 2>&1; then',
614
+ ' timeout 8s mongosh "mongodb://127.0.0.1:$MONGO_PORT/admin?directConnection=true" --quiet --eval "try { rs.initiate({_id: \'rs0\', members: [{ _id: 0, host: \'127.0.0.1:$MONGO_PORT\' }]}) } catch (e) {}" >/dev/null 2>&1 || true',
615
+ ' fi',
616
+ ' sleep 3',
617
+ ' mongo_tcp_ready >/dev/null 2>&1',
618
+ '}',
619
+ 'materialize_live_mongo_runtime() {',
620
+ ' local runtime_file="$ARTIFACT_DIR/.mongo-runtime.json"',
621
+ ' node - "$runtime_file" "$PROJECT_ROOT" "$REPO_ROOT" <<\'RESOLVEIO_QA_MONGO_RUNTIME\'',
622
+ 'const fs = require("fs");',
623
+ 'const path = require("path");',
624
+ 'const [runtimeFile, projectRootArg, repoRootArg] = process.argv.slice(2);',
625
+ 'function parseEnv(filePath) {',
626
+ ' const out = {};',
627
+ ' try {',
628
+ ' for (const raw of fs.readFileSync(filePath, "utf8").split(/\\n/)) {',
629
+ ' const line = raw.trim();',
630
+ ' if (!line || line.startsWith("#")) continue;',
631
+ ' if (line.includes("=")) {',
632
+ ' const index = line.indexOf("=");',
633
+ ' out[line.slice(0, index).trim().replace(/^[\\\'"]|[\\\'"]$/g, "")] = line.slice(index + 1).trim().replace(/^[\\\'"]|[\\\'"],?$/g, "");',
634
+ ' continue;',
635
+ ' }',
636
+ ' const jsonish = line.match(/^[\\\'"]?([A-Z0-9_]+)[\\\'"]?\\s*:\\s*[\\\'"]?(.+?)[\\\'"]?,?$/i);',
637
+ ' if (jsonish) out[jsonish[1]] = jsonish[2].trim();',
638
+ ' }',
639
+ ' } catch (error) {}',
640
+ ' return out;',
641
+ '}',
642
+ 'function readJson(filePath) { try { return JSON.parse(fs.readFileSync(filePath, "utf8")); } catch (error) { return {}; } }',
643
+ 'function pick(obj, keys) { for (const key of keys) { if (obj && typeof obj[key] === "string" && obj[key].trim()) return obj[key].trim(); } return ""; }',
644
+ 'function isLocalMongo(uri) { return /mongodb(?:\\+srv)?:\\/\\/(?:[^@/]+@)?(?:127\\.0\\.0\\.1|localhost)(?::\\d+)?\\//i.test(String(uri || "")); }',
645
+ 'function unique(values) { return Array.from(new Set(values.filter(Boolean).map((value) => path.resolve(String(value))))); }',
646
+ 'const appName = projectRootArg ? path.basename(projectRootArg) : "";',
647
+ 'const runtimeFileCandidates = unique([',
648
+ ' process.env.RESOLVEIO_QA_MONGO_RUNTIME_PATH,',
649
+ ' process.env.RESOLVEIO_SUPPORT_QA_MONGO_RUNTIME_PATH,',
650
+ ' process.env.RESOLVEIO_RUNNER_QA_MONGO_RUNTIME_PATH,',
651
+ ' repoRootArg && path.join(repoRootArg, "mongo-context", ".mongo-runtime.json"),',
652
+ ' projectRootArg && path.join(projectRootArg, "mongo-context", ".mongo-runtime.json"),',
653
+ ' repoRootArg && path.join(repoRootArg, ".resolveio-support-context", "mongo-context", ".mongo-runtime.json"),',
654
+ ' projectRootArg && path.join(projectRootArg, ".resolveio-support-context", "mongo-context", ".mongo-runtime.json"),',
655
+ ' repoRootArg && path.join(repoRootArg, ".resolveio-context", "mongo-context", ".mongo-runtime.json"),',
656
+ ' projectRootArg && path.join(projectRootArg, ".resolveio-context", "mongo-context", ".mongo-runtime.json")',
657
+ ']);',
658
+ 'for (const candidate of runtimeFileCandidates) {',
659
+ ' const parsed = readJson(candidate);',
660
+ ' const uri = pick(parsed, ["mongo_uri", "MONGO_URL"]);',
661
+ ' const database = pick(parsed, ["mongo_db", "DATABASE", "database"]);',
662
+ ' if (uri && !isLocalMongo(uri)) {',
663
+ ' fs.mkdirSync(path.dirname(runtimeFile), { recursive: true });',
664
+ ' fs.writeFileSync(runtimeFile, JSON.stringify({ mongo_uri: uri, mongo_db: database || "" }, null, 2));',
665
+ ' process.exit(0);',
666
+ ' }',
667
+ '}',
668
+ 'const runtimeRoots = unique([',
669
+ ' process.env.RESOLVEIO_QA_RUNTIME_ROOT,',
670
+ ' process.env.RESOLVEIO_SUPPORT_QA_RUNTIME_ROOT,',
671
+ ' projectRootArg && path.join(projectRootArg, "server"),',
672
+ ' projectRootArg,',
673
+ ' repoRootArg,',
674
+ ' appName && path.join("/var/app/current", appName, "server"),',
675
+ ' appName && path.join("/var/app/current", appName),',
676
+ ' appName === "resolveio" && "/var/app/current"',
677
+ ']);',
678
+ 'for (const root of runtimeRoots) {',
679
+ ' const env = Object.assign({}, parseEnv(path.join(root, ".env")), parseEnv(path.join(root, ".env.production")), parseEnv(path.join(root, ".env.runtime")), parseEnv(path.join(root, ".env.codex")));',
680
+ ' const settings = Object.assign({}, readJson(path.join(root, "settings.json")), readJson(path.join(root, "settings.local.json")));',
681
+ ' const uri = pick(env, ["MONGO_URL"]) || pick(settings, ["MONGO_URL", "mongo_url"]);',
682
+ ' const database = pick(env, ["DATABASE"]) || pick(settings, ["DATABASE", "database"]);',
683
+ ' if (uri && !isLocalMongo(uri)) {',
684
+ ' fs.mkdirSync(path.dirname(runtimeFile), { recursive: true });',
685
+ ' fs.writeFileSync(runtimeFile, JSON.stringify({ mongo_uri: uri, mongo_db: database || "" }, null, 2));',
686
+ ' process.exit(0);',
687
+ ' }',
688
+ '}',
689
+ 'const fallbackUri = pick(process.env, ["RESOLVEIO_QA_LIVE_MONGO_URL", "RESOLVEIO_SUPPORT_QA_LIVE_MONGO_URL", "RESOLVEIO_RUNNER_QA_LIVE_MONGO_URL"]);',
690
+ 'if (fallbackUri && !isLocalMongo(fallbackUri)) {',
691
+ ' const fallbackDatabase = pick(process.env, ["RESOLVEIO_QA_LIVE_MONGO_DB", "RESOLVEIO_SUPPORT_QA_LIVE_MONGO_DB", "RESOLVEIO_RUNNER_QA_LIVE_MONGO_DB"]);',
692
+ ' fs.mkdirSync(path.dirname(runtimeFile), { recursive: true });',
693
+ ' fs.writeFileSync(runtimeFile, JSON.stringify({ mongo_uri: fallbackUri, mongo_db: fallbackDatabase || "" }, null, 2));',
694
+ ' process.exit(0);',
695
+ '}',
696
+ 'process.exit(0);',
697
+ 'RESOLVEIO_QA_MONGO_RUNTIME',
698
+ ' if [ -s "$runtime_file" ]; then export RESOLVEIO_QA_MONGO_RUNTIME_PATH="$runtime_file"; fi',
699
+ '}',
700
+ 'prestart_seed_live_data() {',
701
+ ' local required="${RESOLVEIO_RUNNER_QA_LIVE_DATA_REQUIRED:-${RESOLVEIO_SUPPORT_QA_LIVE_DATA_REQUIRED:-false}}"',
702
+ ' truthy "$required" || return 0',
703
+ ' [ -f "$TOOLS_DIR/qa-live-data-seed.js" ] || return 0',
704
+ ' echo "ResolveIO AI runner QA pre-start live data seed/cleanup starting before app server startup." | tee -a "$ARTIFACT_DIR/runner.log"',
705
+ ' if ! start_local_mongo_for_prestart_seed; then',
706
+ ' echo "ResolveIO AI runner QA pre-start local Mongo did not become ready for live-data seed." | tee -a "$ARTIFACT_DIR/runner.log"',
707
+ ' return 7',
708
+ ' fi',
709
+ ' materialize_live_mongo_runtime',
710
+ ' node "$TOOLS_DIR/qa-live-data-seed.js" "$PROJECT_ROOT" >> "$ARTIFACT_DIR/runner.log" 2>&1',
711
+ ' local seed_rc="$?"',
712
+ ' if [ "$seed_rc" != "0" ]; then',
713
+ ' echo "ResolveIO AI runner QA pre-start live data seed failed before app server startup (exit $seed_rc)." | tee -a "$ARTIFACT_DIR/runner.log"',
714
+ ' return "$seed_rc"',
715
+ ' fi',
716
+ ' export RESOLVEIO_QA_PRESTART_MONGO_READY=1',
717
+ ' return 0',
718
+ '}',
445
719
  'if reuse_running_app_if_ready; then RUNNER_REUSED_READY=1; exit 0; fi',
446
720
  'if ! mkdir "$LOCK_DIR" 2>/dev/null; then',
447
721
  ' if janitor_lock_is_live; then',
@@ -465,9 +739,13 @@ function buildResolveIORunnerLocalQaScript() {
465
739
  'janitor_start_heartbeat',
466
740
  'echo "$RUNNER_JANITOR_HEARTBEAT_PID" > "$ARTIFACT_DIR/heartbeat.pid"',
467
741
  'janitor_check_resources || exit $?',
742
+ 'prestart_seed_live_data || exit $?',
468
743
  'if [ -d "$PROJECT_ROOT/server" ]; then',
469
744
  ' SERVER_REQUIRED=1',
470
- ' if node -e "const p=require(process.argv[1]); process.exit(p.scripts&&p.scripts.server?0:1)" "$PROJECT_ROOT/server/package.json" >/dev/null 2>&1; then',
745
+ ' if [ "${RESOLVEIO_QA_PRESTART_MONGO_READY:-}" = "1" ] && [ -x "$PROJECT_ROOT/server/node_modules/gulp/bin/gulp.js" ] && [ -f "$PROJECT_ROOT/server/gulpfile.js" ] && grep -q "mongo-start" "$PROJECT_ROOT/server/gulpfile.js"; then',
746
+ ' echo "ResolveIO AI runner QA using pre-seeded local Mongo; starting server without mongo-start." | tee -a "$ARTIFACT_DIR/runner.log"',
747
+ ' (cd "$PROJECT_ROOT/server" && source "$TOOLS_DIR/env.sh" && if [ -f "$HOME/.nvm/nvm.sh" ]; then source "$HOME/.nvm/nvm.sh"; nvm use 22 >/dev/null 2>&1 || true; fi; node_modules/gulp/bin/gulp.js 2>&1 | tee "$ARTIFACT_DIR/server.log") &',
748
+ ' elif node -e "const p=require(process.argv[1]); process.exit(p.scripts&&p.scripts.server?0:1)" "$PROJECT_ROOT/server/package.json" >/dev/null 2>&1; then',
471
749
  ' (cd "$PROJECT_ROOT/server" && source "$TOOLS_DIR/env.sh" && npm run server 2>&1 | tee "$ARTIFACT_DIR/server.log") &',
472
750
  ' elif [ -x "$PROJECT_ROOT/server/start_server.sh" ]; then',
473
751
  ' (cd "$PROJECT_ROOT/server" && source "$TOOLS_DIR/env.sh" && ./start_server.sh 2>&1 | tee "$ARTIFACT_DIR/server.log") &',
@@ -681,6 +959,11 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
681
959
  'const fs = require("fs");',
682
960
  'const path = require("path");',
683
961
  '',
962
+ 'if (isBuildLane()) {',
963
+ ' console.error("ResolveIO support lane guard: live-data seeding is owned by the QA lane, not the 5.3 build lane.");',
964
+ ' process.exit(86);',
965
+ '}',
966
+ '',
684
967
  'const projectRoot = path.resolve(process.argv[2] || process.cwd());',
685
968
  'const repoRoot = findRepoRoot(projectRoot);',
686
969
  'const artifactDir = path.join(projectRoot, "qa-artifacts");',
@@ -689,11 +972,16 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
689
972
  '',
690
973
  'function writeResult(payload, exitCode = 0) {',
691
974
  ' const full = { ...payload, created_at: new Date().toISOString() };',
975
+ ' normalizeCoverageMatrixFromLiveSeed(full);',
692
976
  ' fs.writeFileSync(resultPath, JSON.stringify(full, null, 2));',
693
977
  ' console.log(JSON.stringify(full, null, 2));',
694
978
  ' process.exit(exitCode);',
695
979
  '}',
696
980
  '',
981
+ 'function isBuildLane() {',
982
+ ' return /^(build|support_build|support-build)$/i.test(String(process.env.RESOLVEIO_SUPPORT_CODEX_LANE || "").trim());',
983
+ '}',
984
+ '',
697
985
  'function findRepoRoot(start) {',
698
986
  ' let current = start;',
699
987
  ' for (let i = 0; i < 8; i += 1) {',
@@ -709,13 +997,62 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
709
997
  ' try { return JSON.parse(fs.readFileSync(filePath, "utf8")); } catch (error) { return null; }',
710
998
  '}',
711
999
  '',
1000
+ 'function normalizeCoverageMatrixFromLiveSeed(payload) {',
1001
+ ' const bolContext = payload && payload.selected && payload.selected.truck_treating_bol_context;',
1002
+ ' const notes = Array.isArray(payload && payload.notes) ? payload.notes.map((note) => String(note || "")) : [];',
1003
+ ' const billingDisabled = notes.some((note) => /billing fixtures are disabled/i.test(note));',
1004
+ ' if (!bolContext || !billingDisabled) return;',
1005
+ ' const matrixPath = path.join(artifactDir, "qa-coverage-matrix.json");',
1006
+ ' const matrix = readJsonIfExists(matrixPath) || { status: "started", seeded: true, source: "qa-live-data-seed" };',
1007
+ ' const browserRoutes = bolContext.browser_routes && typeof bolContext.browser_routes === "object" ? bolContext.browser_routes : {};',
1008
+ ' const routeHints = Array.isArray(bolContext.route_name_hints) ? bolContext.route_name_hints.map((value) => String(value || "").trim()).filter(Boolean) : [];',
1009
+ ' const linkedRoutes = Array.isArray(bolContext.linked_truck_treating_routes) ? bolContext.linked_truck_treating_routes.map((value) => String(value || "").trim()).filter(Boolean) : [];',
1010
+ ' const bolIds = Array.isArray(bolContext.bol_ids) ? bolContext.bol_ids.map((value) => String(value || "").trim()).filter(Boolean) : [];',
1011
+ ' const routeIds = Array.isArray(bolContext.route_ids) ? bolContext.route_ids.map((value) => String(value || "").trim()).filter(Boolean) : [];',
1012
+ ' const truckTreatingRouteIds = Array.isArray(bolContext.truck_treating_route_ids) ? bolContext.truck_treating_route_ids.map((value) => String(value || "").trim()).filter(Boolean) : [];',
1013
+ ' const primaryStatus = String(bolContext.primary_bol_status || "");',
1014
+ ' const delivered = Boolean(bolContext.primary_bol_delivered) || /delivered/i.test(primaryStatus);',
1015
+ ' const hintedBol = routeHints.join(" ");',
1016
+ ' const bolNumber = (/\\bBOL\\s*#?\\s*(\\d+)\\b/i.exec(hintedBol) || [])[1];',
1017
+ ' const bolLabel = bolNumber ? `BOL ${bolNumber}` : (bolIds[0] ? `BOL ${bolIds[0]}` : "the requested BOL");',
1018
+ ' const routeHint = routeHints.find((hint) => !/\\bBOL\\b/i.test(hint) && !/^\\d+$/.test(hint)) || "the ticket route hint";',
1019
+ ' const routeSummary = linkedRoutes.length ? linkedRoutes.join(", ") : (truckTreatingRouteIds.length ? truckTreatingRouteIds.join(", ") : routeIds.slice(0, 6).join(", "));',
1020
+ ' 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.` : "";',
1021
+ ' 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}`;',
1022
+ ' matrix.status = "in_progress";',
1023
+ ' matrix.source = "qa-live-data-seed-live-bol-context";',
1024
+ ' matrix.live_seed_context_artifact = "qa-artifacts/qa-live-data-seed-result.json";',
1025
+ ' 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.";',
1026
+ ' matrix.rows = [',
1027
+ ' { 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"}.` },',
1028
+ ' { 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: "" },',
1029
+ ' { 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: "" }',
1030
+ ' ];',
1031
+ ' fs.writeFileSync(matrixPath, JSON.stringify(matrix, null, 2));',
1032
+ '}',
1033
+ '',
712
1034
  'function preserveExistingSeedResult(reason) {',
713
1035
  ' const existing = readJsonIfExists(resultPath);',
714
1036
  ' const status = String(existing && existing.status || "").toLowerCase();',
715
- ' if (existing && existing.profile === "billing_inventory" && !(existing.selected && existing.selected.qa_billing_fixture)) {',
1037
+ ' const desiredProfile = shouldSeedBillingDashboardFixtures() ? "billing_inventory" : "live_context";',
1038
+ ' if (existing && existing.profile && existing.profile !== desiredProfile) return;',
1039
+ ' if (existing && existing.profile === "billing_inventory" && shouldSeedBillingDashboardFixtures() && !(existing.selected && existing.selected.qa_billing_fixture)) {',
1040
+ ' return;',
1041
+ ' }',
1042
+ ' if (existing && extractBolIdentifiers().length && !(existing.selected && existing.selected.truck_treating_bol_context)) {',
1043
+ ' return;',
1044
+ ' }',
1045
+ ' 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)) {',
1046
+ ' return;',
1047
+ ' }',
1048
+ ' 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")) {',
1049
+ ' return;',
1050
+ ' }',
1051
+ ' if (existing && shouldAutoDiscoverAssetContext() && !(existing.selected && existing.selected.qa_asset_context)) {',
716
1052
  ' return;',
717
1053
  ' }',
718
1054
  ' if (["pass", "needs-data"].includes(status)) {',
1055
+ ' normalizeCoverageMatrixFromLiveSeed(existing);',
719
1056
  ' const preserved = { ...existing, reused_existing: true, reuse_reason: reason, checked_at: new Date().toISOString() };',
720
1057
  ' console.log(JSON.stringify(preserved, null, 2));',
721
1058
  ' process.exit(0);',
@@ -723,9 +1060,6 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
723
1060
  '}',
724
1061
  '',
725
1062
  'function resolveRuntimeSource() {',
726
- ' 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 || "";',
727
- ' 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 || "";',
728
- ' if (envUri) return { uri: envUri, database: envDb };',
729
1063
  ' const explicitRuntimePaths = [',
730
1064
  ' process.env.RESOLVEIO_QA_MONGO_RUNTIME_PATH,',
731
1065
  ' process.env.RESOLVEIO_SUPPORT_QA_MONGO_RUNTIME_PATH,',
@@ -744,6 +1078,9 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
744
1078
  ' const parsed = readJsonIfExists(candidate);',
745
1079
  ' if (parsed && parsed.mongo_uri) return { uri: String(parsed.mongo_uri || ""), database: String(parsed.mongo_db || "") };',
746
1080
  ' }',
1081
+ ' 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 || "";',
1082
+ ' 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 || "";',
1083
+ ' if (envUri) return { uri: envUri, database: envDb };',
747
1084
  ' return { uri: "", database: "" };',
748
1085
  '}',
749
1086
  '',
@@ -759,6 +1096,40 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
759
1096
  ' 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 || ""));',
760
1097
  '}',
761
1098
  '',
1099
+ 'function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }',
1100
+ '',
1101
+ 'async function waitForTargetMongo(MongoClient, targetUri) {',
1102
+ ' const deadline = Date.now() + Number(process.env.RESOLVEIO_QA_LOCAL_MONGO_WAIT_MS || process.env.RESOLVEIO_SUPPORT_QA_LOCAL_MONGO_WAIT_MS || process.env.RESOLVEIO_RUNNER_QA_LOCAL_MONGO_WAIT_MS || 60000);',
1103
+ ' let lastError = null;',
1104
+ ' while (Date.now() < deadline) {',
1105
+ ' const client = new MongoClient(targetUri, { serverSelectionTimeoutMS: 2500 });',
1106
+ ' try {',
1107
+ ' await client.connect();',
1108
+ ' const admin = client.db("admin");',
1109
+ ' await admin.command({ ping: 1 });',
1110
+ ' let hello = null;',
1111
+ ' try { hello = await admin.command({ hello: 1 }); } catch (error) {',
1112
+ ' try { hello = await admin.command({ isMaster: 1 }); } catch (inner) { hello = null; }',
1113
+ ' }',
1114
+ ' const writable = !hello || hello.isWritablePrimary === true || hello.ismaster === true || !hello.setName;',
1115
+ ' if (!writable) {',
1116
+ ' lastError = new Error(`target_local_mongo_not_primary: ${hello && (hello.primary || hello.me || hello.setName) || "unknown"}`);',
1117
+ ' await client.close().catch(() => undefined);',
1118
+ ' await delay(1500);',
1119
+ ' continue;',
1120
+ ' }',
1121
+ ' await client.close().catch(() => undefined);',
1122
+ ' return;',
1123
+ ' } catch (error) {',
1124
+ ' lastError = error;',
1125
+ ' await client.close().catch(() => undefined);',
1126
+ ' await delay(1500);',
1127
+ ' }',
1128
+ ' }',
1129
+ ' const detail = lastError && (lastError.message || String(lastError)) || "local Mongo did not become ready";',
1130
+ ' throw new Error(`target_local_mongo_unavailable: ${detail}`);',
1131
+ '}',
1132
+ '',
762
1133
  'function requireMongo() {',
763
1134
  ' const candidates = [',
764
1135
  ' path.join(projectRoot, "server", "node_modules", "mongodb"),',
@@ -801,6 +1172,61 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
801
1172
  ' return chunks.filter(Boolean).join("\\n");',
802
1173
  '}',
803
1174
  '',
1175
+ 'function isUsefulSeedIdentifier(value) {',
1176
+ ' const cleaned = String(value || "").trim().replace(/^[#:\\-]+|[.,;:)\\]]+$/g, "");',
1177
+ ' if (!cleaned) return false;',
1178
+ ' if (/^00?4\\d{3}$/.test(cleaned)) return false;',
1179
+ ' if (/^(screen|tab|are|which|and|render|context|workflow|build|snapshot|state|charge|plan|data|document|delivery|deliveries)$/i.test(cleaned)) return false;',
1180
+ ' return /\\d/.test(cleaned) || /^[A-Z][A-Z0-9._-]{3,}$/.test(cleaned);',
1181
+ '}',
1182
+ '',
1183
+ 'function extractBolIdentifiers() {',
1184
+ ' const text = readSeedHintText();',
1185
+ ' const identifiers = new Set();',
1186
+ ' const explicit = /\\bBOL\\s*(?:#|no\\.?|number|num)?\\s*[:#-]?\\s*([A-Z0-9][A-Z0-9._-]{2,})\\b/gi;',
1187
+ ' let match;',
1188
+ ' while ((match = explicit.exec(text)) !== null) {',
1189
+ ' const value = String(match[1] || "").trim();',
1190
+ ' if (isUsefulSeedIdentifier(value)) identifiers.add(value);',
1191
+ ' }',
1192
+ ' return Array.from(identifiers).slice(0, 8);',
1193
+ '}',
1194
+ '',
1195
+ 'function shouldAutoDiscoverTruckTreatingBolContext() {',
1196
+ ' const text = readSeedHintText();',
1197
+ ' return /\\b(deliver(?:y|ed|ies)?|bol|bill\\s+of\\s+lading|truck\\s*treat(?:ing)?|flush(?:es)?|selected\\s+treatments?|chemical|red)\\b/i.test(text);',
1198
+ '}',
1199
+ '',
1200
+ 'function shouldAutoDiscoverAssetContext() {',
1201
+ ' const text = readSeedHintText();',
1202
+ ' return /\\b(asset|assets|unit|units|equipment|current\\s+location|location\\s+name|location\\s+names|default\\s+yard|yard)\\b/i.test(text);',
1203
+ '}',
1204
+ '',
1205
+ 'function extractAssetIdentifiers() {',
1206
+ ' const text = readSeedHintText();',
1207
+ ' const identifiers = new Set();',
1208
+ ' const explicit = /\\b(?:asset|unit|equipment)\\s*(?:#|no\\.?|number|num)?\\s*[:#-]?\\s*([A-Z0-9][A-Z0-9._-]{1,})\\b/gi;',
1209
+ ' let match;',
1210
+ ' while ((match = explicit.exec(text)) !== null) {',
1211
+ ' const value = String(match[1] || "").trim();',
1212
+ ' if (isUsefulSeedIdentifier(value)) identifiers.add(value);',
1213
+ ' }',
1214
+ ' if (shouldAutoDiscoverAssetContext()) extractSeedIdentifiers().forEach((value) => identifiers.add(value));',
1215
+ ' return Array.from(identifiers).slice(0, 20);',
1216
+ '}',
1217
+ '',
1218
+ 'function extractRouteNameHints() {',
1219
+ ' const text = readSeedHintText();',
1220
+ ' const routes = new Set();',
1221
+ ' const routePattern = /\\b(?:on|for)\\s+(?:the\\s+)?([A-Za-z0-9][A-Za-z0-9 &._/-]{1,48}?)\\s+route\\b/gi;',
1222
+ ' let match;',
1223
+ ' while ((match = routePattern.exec(text)) !== null) {',
1224
+ ' const routeName = String(match[1] || "").trim().replace(/^(?:the|a|an)\\s+/i, "").replace(/\\s+/g, " ");',
1225
+ ' if (routeName && !/^(?:this|that|selected)$/i.test(routeName)) routes.add(routeName);',
1226
+ ' }',
1227
+ ' return Array.from(routes).slice(0, 8);',
1228
+ '}',
1229
+ '',
804
1230
  'function extractSeedIdentifiers() {',
805
1231
  ' const text = readSeedHintText();',
806
1232
  ' const identifiers = new Set();',
@@ -811,10 +1237,18 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
811
1237
  ' while ((match = numeric.exec(text)) !== null) identifiers.add(match[0]);',
812
1238
  ' return Array.from(identifiers)',
813
1239
  ' .map((value) => String(value || "").trim())',
814
- ' .filter((value) => value && !/^00?4\\d{3}$/.test(value))',
1240
+ ' .filter((value) => isUsefulSeedIdentifier(value))',
815
1241
  ' .slice(0, 20);',
816
1242
  '}',
817
1243
  '',
1244
+ 'function shouldSeedBillingDashboardFixtures() {',
1245
+ ' 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 || "";',
1246
+ ' if (/^(true|1|yes|on)$/i.test(explicit)) return true;',
1247
+ ' if (/^(false|0|no|off)$/i.test(explicit)) return false;',
1248
+ ' const text = readSeedHintText();',
1249
+ ' return /\\b(invoice|invoicing|billing|billable|tax|taxes|surcharge|pricing|line\\s*item|line\\s*items|inventory|checkout|check\\s*out|pick\\s*ticket)\\b/i.test(text);',
1250
+ '}',
1251
+ '',
818
1252
  'function identifierQuery(identifiers) {',
819
1253
  ' const ors = [];',
820
1254
  ' 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"];',
@@ -828,6 +1262,25 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
828
1262
  ' return ors.length ? { $or: ors } : null;',
829
1263
  '}',
830
1264
  '',
1265
+ 'function assetLookupQuery(identifiers) {',
1266
+ ' const ors = [];',
1267
+ ' const fields = ["_id", "asset_number", "asset_number_string", "asset_unit_number", "unit_number", "number", "serial_number", "name"];',
1268
+ ' for (const identifier of identifiers || []) {',
1269
+ ' const value = String(identifier || "").trim();',
1270
+ ' if (!value) continue;',
1271
+ ' const escaped = value.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&");',
1272
+ ' const exactText = new RegExp(`^${escaped}$`, "i");',
1273
+ ' const containsText = new RegExp(escaped, "i");',
1274
+ ' for (const field of fields) ors.push({ [field]: exactText });',
1275
+ ' for (const field of ["asset_number_string", "asset_unit_number", "unit_number", "number", "serial_number", "name"]) ors.push({ [field]: containsText });',
1276
+ ' if (/^\\d+$/.test(value)) {',
1277
+ ' const numericValue = Number(value);',
1278
+ ' for (const field of ["asset_number", "unit_number", "number"]) ors.push({ [field]: numericValue });',
1279
+ ' }',
1280
+ ' }',
1281
+ ' return ors.length ? { $or: ors } : null;',
1282
+ '}',
1283
+ '',
831
1284
  'async function copyByIds(sourceDb, targetDb, collectionName, ids, summary, limit = 100) {',
832
1285
  ' const cleanIds = unique(ids).slice(0, limit);',
833
1286
  ' if (!cleanIds.length) return [];',
@@ -846,6 +1299,203 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
846
1299
  ' return docs;',
847
1300
  '}',
848
1301
  '',
1302
+ 'function bolLookupQuery(identifiers) {',
1303
+ ' const ors = [];',
1304
+ ' for (const identifier of identifiers || []) {',
1305
+ ' const value = String(identifier || "").trim();',
1306
+ ' if (!value) continue;',
1307
+ ' const escaped = value.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&");',
1308
+ ' const contains = new RegExp(`(^|[^0-9A-Za-z])${escaped}([^0-9A-Za-z]|$)`, "i");',
1309
+ ' ors.push({ _id: value }, { bol_number: value }, { bol_string: value }, { bol_number: contains }, { bol_string: contains });',
1310
+ ' if (/^\\d+$/.test(value)) ors.push({ bol_number: Number(value) });',
1311
+ ' }',
1312
+ ' return ors.length ? { $or: ors } : null;',
1313
+ '}',
1314
+ '',
1315
+ 'async function discoverTruckTreatingBolIds(sourceDb, routeHints, limit = 5) {',
1316
+ ' const routeRegexes = (routeHints || []).map((hint) => new RegExp(String(hint).replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&"), "i"));',
1317
+ ' const routeHintMatch = routeRegexes.length ? { $or: [',
1318
+ ' { location: { $in: routeRegexes } },',
1319
+ ' { well_group: { $in: routeRegexes } },',
1320
+ ' { route: { $in: routeRegexes } },',
1321
+ ' { route_name: { $in: routeRegexes } }',
1322
+ ' ] } : null;',
1323
+ ' const baseMatch = {',
1324
+ ' id_bol: { $type: "string", $ne: "" },',
1325
+ ' items: { $elemMatch: { item_type: { $in: ["Service", "Flush"] } } }',
1326
+ ' };',
1327
+ ' const pipeline = [',
1328
+ ' { $match: routeHintMatch ? { $and: [baseMatch, routeHintMatch] } : baseMatch },',
1329
+ ' { $sort: { date: -1, date_ship: -1, updatedAt: -1 } },',
1330
+ ' { $group: { _id: "$id_bol", route_count: { $sum: 1 }, latest: { $max: "$updatedAt" } } },',
1331
+ ' { $sort: { latest: -1, route_count: -1 } },',
1332
+ ' { $limit: limit },',
1333
+ ' { $project: { _id: 1 } }',
1334
+ ' ];',
1335
+ ' const rows = await sourceDb.collection("production-routes").aggregate(pipeline).toArray();',
1336
+ ' return rows.map((row) => row && row._id).filter(Boolean);',
1337
+ '}',
1338
+ '',
1339
+ 'function collectNestedStringValues(docs, keys) {',
1340
+ ' const values = new Set();',
1341
+ ' const visit = (value) => {',
1342
+ ' if (!value) return;',
1343
+ ' if (Array.isArray(value)) { value.forEach(visit); return; }',
1344
+ ' if (typeof value === "object") {',
1345
+ ' for (const key of keys) {',
1346
+ ' const entry = value[key];',
1347
+ ' if (typeof entry === "string" && entry.trim()) values.add(entry.trim());',
1348
+ ' else if (Array.isArray(entry) || (entry && typeof entry === "object")) visit(entry);',
1349
+ ' }',
1350
+ ' for (const nested of Object.values(value)) if (Array.isArray(nested)) visit(nested);',
1351
+ ' }',
1352
+ ' };',
1353
+ ' visit(docs || []);',
1354
+ ' return Array.from(values);',
1355
+ '}',
1356
+ '',
1357
+ 'async function copyTruckTreatingBolContext(sourceDb, targetDb, summary) {',
1358
+ ' const bolIdentifiers = extractBolIdentifiers();',
1359
+ ' const routeHints = extractRouteNameHints();',
1360
+ ' summary.selected.bol_identifiers = bolIdentifiers;',
1361
+ ' if (routeHints.length) summary.selected.route_name_hints = routeHints;',
1362
+ ' let query = bolLookupQuery(bolIdentifiers);',
1363
+ ' let discoveryMode = "";',
1364
+ ' if (!query && shouldAutoDiscoverTruckTreatingBolContext()) {',
1365
+ ' const discoveredBolIds = await discoverTruckTreatingBolIds(sourceDb, routeHints, 8);',
1366
+ ' if (discoveredBolIds.length) {',
1367
+ ' summary.selected.discovered_truck_treating_bol_ids = discoveredBolIds;',
1368
+ ' query = { _id: { $in: discoveredBolIds } };',
1369
+ ' discoveryMode = "auto_discovered_from_live_production_routes";',
1370
+ ' }',
1371
+ ' }',
1372
+ ' if (!query) return [];',
1373
+ ' let bols = await copyQuery(sourceDb, targetDb, "bols", query, summary, 8, { date_shipped: -1, date: -1, updatedAt: -1 });',
1374
+ ' if (!bols.length) {',
1375
+ ' summary.notes.push(`No live BOL matched ticket BOL identifiers: ${bolIdentifiers.join(", ")}`);',
1376
+ ' if (shouldAutoDiscoverTruckTreatingBolContext()) {',
1377
+ ' const discoveredBolIds = await discoverTruckTreatingBolIds(sourceDb, routeHints, 8);',
1378
+ ' if (discoveredBolIds.length) {',
1379
+ ' summary.selected.discovered_truck_treating_bol_ids = discoveredBolIds;',
1380
+ ' discoveryMode = "auto_discovered_after_identifier_miss";',
1381
+ ' bols = await copyQuery(sourceDb, targetDb, "bols", { _id: { $in: discoveredBolIds } }, summary, 8, { date_shipped: -1, date: -1, updatedAt: -1 });',
1382
+ ' }',
1383
+ ' }',
1384
+ ' if (!bols.length) return [];',
1385
+ ' }',
1386
+ ' const bolIds = bols.map((doc) => doc._id).filter(Boolean);',
1387
+ ' const productionRoutes = await copyQuery(sourceDb, targetDb, "production-routes", { id_bol: { $in: bolIds } }, summary, 600, { route_order: 1, route_number: 1 });',
1388
+ ' const routeIds = productionRoutes.map((doc) => doc._id).filter(Boolean);',
1389
+ ' const truckTreatingRouteIds = idValues(productionRoutes, ["id_truck_treating_route"]);',
1390
+ ' const productionSalesOrderIds = idValues(productionRoutes, ["id_pso", "id_activity", "id_production_sales_order", "id_sales_order"]);',
1391
+ ' const productionDeliveries = await copyQuery(sourceDb, targetDb, "production-deliveries", { $or: [{ id_bol: { $in: bolIds } }, { id_activity: { $in: productionSalesOrderIds } }] }, summary, 600, { route_order: 1, updatedAt: -1 });',
1392
+ ' const routeTruckTreatDeliveryIds = collectNestedStringValues(productionRoutes, ["id_truck_treat_delivery", "id_truck_treating_delivery"]);',
1393
+ ' const truckTreatingDeliveries = await copyQuery(sourceDb, targetDb, "truck-treating-deliveries", { $or: [{ id_bol: { $in: bolIds } }, { _id: { $in: routeTruckTreatDeliveryIds } }] }, summary, 600, { route_order: 1, updatedAt: -1 });',
1394
+ ' const copiedTruckTreatingRoutes = await copyByIds(sourceDb, targetDb, "truck-treating-routes", truckTreatingRouteIds, summary, 200);',
1395
+ ' await copyByIds(sourceDb, targetDb, "production-sales-orders", productionSalesOrderIds, summary, 200);',
1396
+ ' const locationIds = unique([...idValues(productionRoutes, ["id_location"]), ...idValues(productionDeliveries, ["id_location"]), ...idValues(truckTreatingDeliveries, ["id_location"])]);',
1397
+ ' const wellGroupIds = unique([...idValues(productionRoutes, ["id_well_group"]), ...idValues(productionDeliveries, ["id_well_group"]), ...idValues(truckTreatingDeliveries, ["id_well_group"])]);',
1398
+ ' const copiedLocations = await copyByIds(sourceDb, targetDb, "production-locations", locationIds, summary, 300);',
1399
+ ' const copiedWellGroups = await copyByIds(sourceDb, targetDb, "well-groups", wellGroupIds, summary, 300);',
1400
+ ' const copiedTreatmentPlans = await copyQuery(sourceDb, targetDb, "treatment-plans", { $or: [',
1401
+ ' { "truck_treating.id_truck_treating_route": { $in: truckTreatingRouteIds } },',
1402
+ ' { id_location: { $in: locationIds } },',
1403
+ ' { id_well_group: { $in: wellGroupIds } }',
1404
+ ' ] }, summary, 800, { route_order: 1, updatedAt: -1 });',
1405
+ ' const itemIds = unique([',
1406
+ ' ...collectNestedStringValues(productionRoutes, ["id_item", "id_chemical"]),',
1407
+ ' ...idValues(productionDeliveries, ["id_item", "id_chemical"]),',
1408
+ ' ...idValues(truckTreatingDeliveries, ["id_item", "id_chemical"]),',
1409
+ ' ...idValues(copiedTreatmentPlans, ["id_item", "id_chemical"])',
1410
+ ' ]);',
1411
+ ' await copyByIds(sourceDb, targetDb, "items", itemIds, summary, 300);',
1412
+ ' await copyByIds(sourceDb, targetDb, "chemicals", itemIds, summary, 300);',
1413
+ ' await copyByIds(sourceDb, targetDb, "customers", unique([...idValues(copiedLocations, ["id_customer"]), ...idValues(copiedWellGroups, ["id_customer"]), ...idValues(productionDeliveries, ["id_customer"]), ...idValues(truckTreatingDeliveries, ["id_customer"])]), summary, 100);',
1414
+ ' 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);',
1415
+ ' await copyQuery(sourceDb, targetDb, "chemical-field-transfers", { id_bol: { $in: bolIds } }, summary, 200, { updatedAt: -1 });',
1416
+ ' await copyQuery(sourceDb, targetDb, "generic-samples", { id_bol: { $in: bolIds } }, summary, 200, { updatedAt: -1 });',
1417
+ ' const linkedRouteNames = copiedTruckTreatingRoutes.map((doc) => String(doc.name || doc.route || doc.truck_treating_route || doc._id || "")).filter(Boolean);',
1418
+ ' const routeHintMatched = routeHints.length ? routeHints.some((hint) => linkedRouteNames.some((name) => name.toLowerCase().includes(hint.toLowerCase()))) : null;',
1419
+ ' 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"])]);',
1420
+ ' const bolStatuses = bols.map((doc) => ({',
1421
+ ' id: String(doc && doc._id || ""),',
1422
+ ' bol_number: String(doc && (doc.bol_string || doc.bol_number || "") || ""),',
1423
+ ' status: String(doc && doc.status || ""),',
1424
+ ' delivered: /delivered/i.test(String(doc && doc.status || ""))',
1425
+ ' })).filter((entry) => entry.id);',
1426
+ ' const primaryDriverId = driverIds[0] || "";',
1427
+ ' const primaryBolId = bolIds[0] || "";',
1428
+ ' const primaryBolStatus = String((bolStatuses.find((entry) => entry.id === primaryBolId) || bolStatuses[0] || {}).status || "");',
1429
+ ' const primaryBolDelivered = /delivered/i.test(primaryBolStatus);',
1430
+ ' const browserRoutes = primaryDriverId && primaryBolId ? {',
1431
+ ' delivery: `/dashboard/driver/truck-treating/bol-delivery/${primaryDriverId}/${primaryBolId}`,',
1432
+ ' detail: `/dashboard/driver/truck-treating/bol-detail/${primaryDriverId}/${primaryBolId}`,',
1433
+ ' list: `/dashboard/driver/truck-treating/bol-list/${primaryDriverId}`',
1434
+ ' } : {};',
1435
+ ' summary.selected.truck_treating_bol_context = {',
1436
+ ' bol_ids: bolIds,',
1437
+ ' driver_ids: driverIds,',
1438
+ ' route_ids: routeIds.slice(0, 100),',
1439
+ ' route_count: productionRoutes.length,',
1440
+ ' truck_treating_route_ids: truckTreatingRouteIds,',
1441
+ ' linked_truck_treating_routes: linkedRouteNames,',
1442
+ ' browser_routes: browserRoutes,',
1443
+ ' bol_statuses: bolStatuses,',
1444
+ ' primary_bol_status: primaryBolStatus,',
1445
+ ' primary_bol_delivered: primaryBolDelivered,',
1446
+ ' delivery_route_is_action_route: true,',
1447
+ ' delivery_route_requires_not_delivered_bol: true,',
1448
+ ' route_name_hints: routeHints,',
1449
+ ' route_hint_matched: routeHintMatched',
1450
+ ' };',
1451
+ ' if (discoveryMode) summary.selected.truck_treating_bol_context.discovery_mode = discoveryMode;',
1452
+ ' if (browserRoutes.delivery) summary.notes.push(`Derived truck-treating BOL browser target from live Mongo: ${browserRoutes.delivery}`);',
1453
+ ' 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.");',
1454
+ ' 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.`);',
1455
+ ' 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.`);',
1456
+ ' 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).`);',
1457
+ ' return [...bols, ...productionRoutes, ...productionDeliveries, ...truckTreatingDeliveries, ...copiedTreatmentPlans];',
1458
+ '}',
1459
+ '',
1460
+ 'async function copyTicketAssetContext(sourceDb, targetDb, summary) {',
1461
+ ' if (!shouldAutoDiscoverAssetContext()) return [];',
1462
+ ' const assetIdentifiers = extractAssetIdentifiers();',
1463
+ ' summary.selected.asset_identifiers = assetIdentifiers;',
1464
+ ' const query = assetLookupQuery(assetIdentifiers);',
1465
+ ' if (!query) return [];',
1466
+ ' const assets = await copyQuery(sourceDb, targetDb, "assets", query, summary, 20, { updatedAt: -1, asset_number: 1 });',
1467
+ ' if (!assets.length) {',
1468
+ ' summary.notes.push(`No live assets matched ticket identifiers: ${assetIdentifiers.join(", ")}`);',
1469
+ ' return [];',
1470
+ ' }',
1471
+ ' const yardIds = unique([',
1472
+ ' ...idValues(assets, ["id_yard", "id_default_yard"]),',
1473
+ ' ...collectNestedStringValues(assets, ["id", "id_yard"])',
1474
+ ' ]);',
1475
+ ' const locationIds = unique([',
1476
+ ' ...idValues(assets, ["id_location", "id_current_location", "id_default_location"]),',
1477
+ ' ...collectNestedStringValues(assets, ["id_location"])',
1478
+ ' ]);',
1479
+ ' const userIds = unique([',
1480
+ ' ...idValues(assets, ["id_user", "id_driver", "id_created_by", "id_updated_by"]),',
1481
+ ' ...collectNestedStringValues(assets, ["id_user", "id_driver", "id_created_by", "id_updated_by"])',
1482
+ ' ]);',
1483
+ ' await copyByIds(sourceDb, targetDb, "yards", yardIds, summary, 100);',
1484
+ ' await copyByIds(sourceDb, targetDb, "production-locations", locationIds, summary, 100);',
1485
+ ' await copyByIds(sourceDb, targetDb, "users", userIds, summary, 100);',
1486
+ ' const assetIds = assets.map((doc) => doc._id).filter(Boolean);',
1487
+ ' const assetNumbers = assets.map((doc) => String(doc.asset_number || doc.asset_number_string || doc.asset_unit_number || doc.unit_number || "")).filter(Boolean);',
1488
+ ' summary.selected.qa_asset_context = {',
1489
+ ' asset_ids: assetIds,',
1490
+ ' asset_numbers: assetNumbers,',
1491
+ ' yard_ids: yardIds,',
1492
+ ' production_location_ids: locationIds,',
1493
+ ' browser_routes: assetIds[0] ? { list: "/asset/list", detail: `/asset/detail/${assetIds[0]}`, edit: `/asset/edit/${assetIds[0]}` } : { list: "/asset/list" }',
1494
+ ' };',
1495
+ ' 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).`);',
1496
+ ' return assets;',
1497
+ '}',
1498
+ '',
849
1499
  'async function ensureBillingSurchargePricingFixtures(targetDb, summary, serviceItems, customerIds) {',
850
1500
  ' const now = new Date();',
851
1501
  ' const activeServiceItems = (serviceItems || []).filter((doc) => {',
@@ -885,6 +1535,21 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
885
1535
  ' if (pricingDocs.length) summary.notes.push(`Ensured ${pricingDocs.length} local pricing-items for service/misc surcharge override/default QA.`);',
886
1536
  '}',
887
1537
  '',
1538
+ 'async function cleanupSyntheticBillingDashboardQaFixtures(targetDb, summary) {',
1539
+ ' const fixtureIdQuery = { _id: /^qa-billing-/ };',
1540
+ ' const fixtureCollections = ["customers", "yards", "items", "production-locations", "bols", "production-sales-orders", "production-deliveries", "invoices"];',
1541
+ ' let removed = 0;',
1542
+ ' for (const collectionName of fixtureCollections) {',
1543
+ ' const result = await targetDb.collection(collectionName).deleteMany(fixtureIdQuery).catch(() => ({ deletedCount: 0 }));',
1544
+ ' removed += Number(result && result.deletedCount || 0);',
1545
+ ' }',
1546
+ ' const invoiceResult = await targetDb.collection("invoices").deleteMany({ $or: [{ invoice_string: /^QA-INV-/ }, { invoice_number: 4333 }] }).catch(() => ({ deletedCount: 0 }));',
1547
+ ' removed += Number(invoiceResult && invoiceResult.deletedCount || 0);',
1548
+ ' const psoResult = await targetDb.collection("production-sales-orders").deleteMany({ $or: [{ order_number_string: /^QA-PSO-/ }, { activity_number: /^QA-PSO-/ }] }).catch(() => ({ deletedCount: 0 }));',
1549
+ ' removed += Number(psoResult && psoResult.deletedCount || 0);',
1550
+ ' if (removed) summary.notes.push(`Removed ${removed} stale synthetic billing dashboard QA fixture document(s) from the local Mongo profile.`);',
1551
+ '}',
1552
+ '',
888
1553
  'async function ensureBillingDashboardQaFixtures(targetDb, summary, customerIds, yardIds, serviceItems) {',
889
1554
  ' const now = new Date();',
890
1555
  ' const customerId = (customerIds && customerIds[0]) || "qa-billing-customer";',
@@ -990,9 +1655,16 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
990
1655
  '}',
991
1656
  '',
992
1657
  'async function seedBillingInventory(sourceDb, targetDb) {',
993
- ' const summary = { profile: "billing_inventory", collections: {}, selected: {}, notes: [] };',
1658
+ ' const includeBillingFixtures = shouldSeedBillingDashboardFixtures();',
1659
+ ' const summary = { profile: includeBillingFixtures ? "billing_inventory" : "live_context", collections: {}, selected: {}, notes: [] };',
994
1660
  ' const identifiers = extractSeedIdentifiers();',
995
1661
  ' summary.selected.seed_identifiers = identifiers;',
1662
+ ' const truckTreatingBolContextDocs = await copyTruckTreatingBolContext(sourceDb, targetDb, summary);',
1663
+ ' const ticketAssetContextDocs = await copyTicketAssetContext(sourceDb, targetDb, summary);',
1664
+ ' if (!includeBillingFixtures) {',
1665
+ ' summary.notes.push("Using live-context seed profile: billing dashboard fixtures are disabled because ticket context does not mention billing, invoices, taxes, inventory, pricing, surcharges, checkout, or pick tickets.");',
1666
+ ' await cleanupSyntheticBillingDashboardQaFixtures(targetDb, summary);',
1667
+ ' }',
996
1668
  ' const billableDeliveryQuery = { $and: [',
997
1669
  ' { $or: [{ type: "Delivery" }, { type: "Pickup" }] },',
998
1670
  ' { $or: [{ consignment: { $exists: false } }, { consignment: false }] },',
@@ -1027,7 +1699,7 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
1027
1699
  ' summary.notes.push("No live billable delivery or truck-treatment records matched the Billing Dashboard awaiting-invoice filters.");',
1028
1700
  ' }',
1029
1701
  '',
1030
- ' const sourceDocs = [...productionDeliveries, ...truckTreatingDeliveries];',
1702
+ ' const sourceDocs = [...truckTreatingBolContextDocs, ...ticketAssetContextDocs, ...productionDeliveries, ...truckTreatingDeliveries];',
1031
1703
  ' const customerIds = idValues(sourceDocs, ["id_customer"]);',
1032
1704
  ' const locationIds = idValues(sourceDocs, ["id_location"]);',
1033
1705
  ' const wellGroupIds = idValues(sourceDocs, ["id_well_group"]);',
@@ -1058,30 +1730,32 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
1058
1730
  ' ] }, summary, 40, { updatedAt: -1 });',
1059
1731
  ' summary.selected.service_or_surcharge_items = copiedServiceItems.map((doc) => doc._id);',
1060
1732
  '',
1061
- ' await copyQuery(sourceDb, targetDb, "sales-taxes", {}, summary, 80, { updatedAt: -1 });',
1062
- ' await copyQuery(sourceDb, targetDb, "state-counties", {}, summary, 80, { updatedAt: -1 });',
1063
- ' await copyQuery(sourceDb, targetDb, "accounting-codes", {}, summary, 80, { updatedAt: -1 });',
1064
- ' await copyQuery(sourceDb, targetDb, "pricing-items", { $or: [',
1065
- ' { sub_type: { $in: ["misc", "service"] } },',
1066
- ' { id_item: { $in: copiedServiceItems.map((doc) => doc._id) } },',
1067
- ' { name: /surcharge|fuel|delivery|service|misc/i }',
1068
- ' ] }, summary, 80, { updatedAt: -1 });',
1069
- ' await ensureBillingSurchargePricingFixtures(targetDb, summary, copiedServiceItems, customerIds);',
1070
- ' await ensureBillingDashboardQaFixtures(targetDb, summary, customerIds, yardIds, copiedServiceItems);',
1733
+ ' if (includeBillingFixtures) {',
1734
+ ' await copyQuery(sourceDb, targetDb, "sales-taxes", {}, summary, 80, { updatedAt: -1 });',
1735
+ ' await copyQuery(sourceDb, targetDb, "state-counties", {}, summary, 80, { updatedAt: -1 });',
1736
+ ' await copyQuery(sourceDb, targetDb, "accounting-codes", {}, summary, 80, { updatedAt: -1 });',
1737
+ ' await copyQuery(sourceDb, targetDb, "pricing-items", { $or: [',
1738
+ ' { sub_type: { $in: ["misc", "service"] } },',
1739
+ ' { id_item: { $in: copiedServiceItems.map((doc) => doc._id) } },',
1740
+ ' { name: /surcharge|fuel|delivery|service|misc/i }',
1741
+ ' ] }, summary, 80, { updatedAt: -1 });',
1742
+ ' await ensureBillingSurchargePricingFixtures(targetDb, summary, copiedServiceItems, customerIds);',
1743
+ ' await ensureBillingDashboardQaFixtures(targetDb, summary, customerIds, yardIds, copiedServiceItems);',
1071
1744
  '',
1072
- ' const inventoryLocationQuery = { $or: [',
1073
- ' { id_item: { $in: unique([...itemIds, ...copiedItems.map((doc) => doc._id), ...copiedServiceItems.map((doc) => doc._id)]) } },',
1074
- ' { id_chemical: { $in: unique([...itemIds, ...copiedChemicals.map((doc) => doc._id)]) } },',
1075
- ' { id_yard: { $in: yardIds } }',
1076
- ' ] };',
1077
- ' const inventoryLocations = await copyQuery(sourceDb, targetDb, "inventory-locations", inventoryLocationQuery, summary, 80, { updatedAt: -1 });',
1078
- ' await copyQuery(sourceDb, targetDb, "inventory-transactions", { $or: [',
1079
- ' { id_inventory_location: { $in: inventoryLocations.map((doc) => doc._id) } },',
1080
- ' { id_item: { $in: itemIds } },',
1081
- ' { id_chemical: { $in: itemIds } }',
1082
- ' ] }, summary, 120, { date: -1, updatedAt: -1 });',
1745
+ ' const inventoryLocationQuery = { $or: [',
1746
+ ' { id_item: { $in: unique([...itemIds, ...copiedItems.map((doc) => doc._id), ...copiedServiceItems.map((doc) => doc._id)]) } },',
1747
+ ' { id_chemical: { $in: unique([...itemIds, ...copiedChemicals.map((doc) => doc._id)]) } },',
1748
+ ' { id_yard: { $in: yardIds } }',
1749
+ ' ] };',
1750
+ ' const inventoryLocations = await copyQuery(sourceDb, targetDb, "inventory-locations", inventoryLocationQuery, summary, 80, { updatedAt: -1 });',
1751
+ ' await copyQuery(sourceDb, targetDb, "inventory-transactions", { $or: [',
1752
+ ' { id_inventory_location: { $in: inventoryLocations.map((doc) => doc._id) } },',
1753
+ ' { id_item: { $in: itemIds } },',
1754
+ ' { id_chemical: { $in: itemIds } }',
1755
+ ' ] }, summary, 120, { date: -1, updatedAt: -1 });',
1756
+ ' }',
1083
1757
  '',
1084
- ' summary.ready = productionDeliveries.length > 0 || truckTreatingDeliveries.length > 0;',
1758
+ ' summary.ready = truckTreatingBolContextDocs.length > 0 || ticketAssetContextDocs.length > 0 || productionDeliveries.length > 0 || truckTreatingDeliveries.length > 0;',
1085
1759
  ' return summary;',
1086
1760
  '}',
1087
1761
  '',
@@ -1095,6 +1769,7 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
1095
1769
  ' }',
1096
1770
  ' if (!isLocalMongoUri(targetUri)) writeResult({ status: "failed", reason: "target_mongo_must_be_localhost", target_uri_redacted: redactUri(targetUri), result_path: resultPath }, 3);',
1097
1771
  ' if (String(source.uri) === String(targetUri)) writeResult({ status: "failed", reason: "source_and_target_mongo_match", result_path: resultPath }, 3);',
1772
+ ' await waitForTargetMongo(MongoClient, targetUri);',
1098
1773
  ' const sourceClient = new MongoClient(source.uri, { readPreference: "secondaryPreferred", serverSelectionTimeoutMS: 15000 });',
1099
1774
  ' const targetClient = new MongoClient(targetUri, { serverSelectionTimeoutMS: 15000 });',
1100
1775
  ' try {',
@@ -1124,6 +1799,10 @@ function buildResolveIORunnerBugfixComparisonQaScript() {
1124
1799
  'set -u',
1125
1800
  'TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"',
1126
1801
  'source "$TOOLS_DIR/env.sh"',
1802
+ 'if resolveio_support_codex_lane_is_build 2>/dev/null; then',
1803
+ ' resolveio_support_codex_block_build_lane_qa',
1804
+ ' exit 86',
1805
+ 'fi',
1127
1806
  'PROJECT_ROOT="${1:-}"',
1128
1807
  'if [ -z "$PROJECT_ROOT" ]; then',
1129
1808
  ' echo "Usage: $0 <project-root> [baseline-ref] -- <qa-command...>" >&2',
@@ -1218,11 +1897,33 @@ function buildResolveIORunnerBugfixComparisonQaScript() {
1218
1897
  ' export RESOLVEIO_SUPPORT_QA_PHASE="$phase"',
1219
1898
  ' export RESOLVEIO_RUNNER_QA_ARTIFACT_DIR="$ARTIFACT_DIR/$phase"',
1220
1899
  ' export RESOLVEIO_SUPPORT_QA_ARTIFACT_DIR="$ARTIFACT_DIR/$phase"',
1900
+ ' export RESOLVEIO_RUNNER_QA_PROJECT_ROOT="$PROJECT_ROOT"',
1901
+ ' export RESOLVEIO_SUPPORT_QA_PROJECT_ROOT="$PROJECT_ROOT"',
1902
+ ' export RESOLVEIO_RUNNER_QA_TOOLS_DIR="$TOOLS_DIR"',
1903
+ ' export RESOLVEIO_SUPPORT_QA_TOOLS_DIR="$TOOLS_DIR"',
1221
1904
  ' mkdir -p "$ARTIFACT_DIR/$phase"',
1222
1905
  ' find "$ARTIFACT_DIR/$phase" -maxdepth 1 -type f \\( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" -o -name "*.webp" -o -name "*.json" -o -name "*.txt" -o -name "*.log" -o -name "*.zip" \\) -delete 2>/dev/null || true',
1223
1906
  ' find "$ARTIFACT_DIR" -maxdepth 1 -type f \\( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" -o -name "*.webp" -o -name "*proof.json" -o -name "auth-bootstrap-result.json" \\) -delete 2>/dev/null || true',
1907
+ ' RESOLVEIO_SUPPORT_QA_KEEPALIVE=true RESOLVEIO_RUNNER_QA_KEEPALIVE=true "$TOOLS_DIR/run-local-qa.sh" "$PROJECT_ROOT"',
1908
+ ' local stack_rc="$?"',
1909
+ ' if [ "$stack_rc" != "0" ]; then',
1910
+ ' echo "ResolveIO bugfix comparison QA $phase phase blocked: local QA stack did not start (exit $stack_rc)." | tee "$ARTIFACT_DIR/$phase/qa-command.log"',
1911
+ ' snapshot_phase_artifacts "$phase"',
1912
+ ' stop_local_qa',
1913
+ ' return "$stack_rc"',
1914
+ ' fi',
1915
+ ' if [ -f "$TOOLS_DIR/qa-live-data-seed.js" ]; then',
1916
+ ' node "$TOOLS_DIR/qa-live-data-seed.js" "$PROJECT_ROOT"',
1917
+ ' local seed_rc="$?"',
1918
+ ' if [ "$seed_rc" != "0" ]; then',
1919
+ ' echo "ResolveIO bugfix comparison QA $phase phase blocked: live data seed failed (exit $seed_rc)." | tee "$ARTIFACT_DIR/$phase/qa-command.log"',
1920
+ ' snapshot_phase_artifacts "$phase"',
1921
+ ' stop_local_qa',
1922
+ ' return "$seed_rc"',
1923
+ ' fi',
1924
+ ' fi',
1224
1925
  ' set +e',
1225
- ' (cd "$REPO_ROOT" && "${QA_COMMAND[@]}") 2>&1 | tee "$ARTIFACT_DIR/$phase/qa-command.log"',
1926
+ ' (cd "$PROJECT_ROOT" && "${QA_COMMAND[@]}") 2>&1 | tee "$ARTIFACT_DIR/$phase/qa-command.log"',
1226
1927
  ' local rc="${PIPESTATUS[0]}"',
1227
1928
  ' set +e',
1228
1929
  ' snapshot_phase_artifacts "$phase"',
@@ -1266,10 +1967,15 @@ function buildResolveIORunnerQaWorkflowProbeScript() {
1266
1967
  'const https = require("https");',
1267
1968
  'const path = require("path");',
1268
1969
  '',
1970
+ 'if (isBuildLane()) {',
1971
+ ' console.error("ResolveIO support lane guard: browser workflow probes are owned by the QA lane, not the 5.3 build lane.");',
1972
+ ' process.exit(86);',
1973
+ '}',
1974
+ '',
1269
1975
  'const projectRoot = path.resolve(process.argv[2] || process.cwd());',
1270
- 'const routeArg = process.argv[3] || process.env.RESOLVEIO_RUNNER_QA_TARGET_ROUTE || process.env.RESOLVEIO_SUPPORT_QA_TARGET_ROUTE || "/";',
1271
- 'const targetRoute = routeArg.startsWith("/") ? routeArg : `/${routeArg}`;',
1272
1976
  'const artifactDir = path.resolve(process.env.RESOLVEIO_RUNNER_QA_ARTIFACT_DIR || process.env.RESOLVEIO_SUPPORT_QA_ARTIFACT_DIR || path.join(projectRoot, "qa-artifacts"));',
1977
+ 'const routeArg = process.argv[3] || process.env.RESOLVEIO_RUNNER_QA_TARGET_ROUTE || process.env.RESOLVEIO_SUPPORT_QA_TARGET_ROUTE || "";',
1978
+ 'const targetRoute = resolveTargetRoute(routeArg);',
1273
1979
  'const matrixPath = path.join(artifactDir, "qa-coverage-matrix.json");',
1274
1980
  'const resultPath = path.join(artifactDir, "qa-workflow-probe-result.json");',
1275
1981
  'const passScreenshotPath = path.join(artifactDir, "qa-workflow-route-ready.jpg");',
@@ -1281,10 +1987,37 @@ function buildResolveIORunnerQaWorkflowProbeScript() {
1281
1987
  'const viewportWidth = Number(process.env.RESOLVEIO_RUNNER_QA_VIEWPORT_WIDTH || process.env.RESOLVEIO_SUPPORT_QA_VIEWPORT_WIDTH || 1920);',
1282
1988
  'const viewportHeight = Number(process.env.RESOLVEIO_RUNNER_QA_VIEWPORT_HEIGHT || process.env.RESOLVEIO_SUPPORT_QA_VIEWPORT_HEIGHT || 1080);',
1283
1989
  '',
1990
+ 'function isBuildLane() {',
1991
+ ' return /^(build|support_build|support-build)$/i.test(String(process.env.RESOLVEIO_SUPPORT_CODEX_LANE || "").trim());',
1992
+ '}',
1993
+ '',
1284
1994
  'function stripTrailingSlash(value) { return String(value || "").replace(/\\/+$/, ""); }',
1285
1995
  'function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }',
1286
1996
  'function writeJson(filePath, payload) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, JSON.stringify(payload, null, 2)); }',
1287
1997
  'function readJson(filePath) { try { return JSON.parse(fs.readFileSync(filePath, "utf8")); } catch (error) { return null; } }',
1998
+ 'function normalizeRoute(candidate) {',
1999
+ ' const value = String(candidate || "").trim();',
2000
+ ' if (!value) return "";',
2001
+ ' if (/^https?:\\/\\//i.test(value)) {',
2002
+ ' try { return new URL(value).pathname || "/"; } catch (error) { return ""; }',
2003
+ ' }',
2004
+ ' return value.startsWith("/") ? value : `/${value}`;',
2005
+ '}',
2006
+ 'function resolveTargetRoute(explicitRoute) {',
2007
+ ' const explicit = normalizeRoute(explicitRoute);',
2008
+ ' if (explicit) return explicit;',
2009
+ ' const seedCandidates = [',
2010
+ ' path.join(artifactDir, "qa-live-data-seed-result.json"),',
2011
+ ' path.join(artifactDir, "candidate", "qa-live-data-seed-result.json")',
2012
+ ' ];',
2013
+ ' for (const seedPath of seedCandidates) {',
2014
+ ' const seed = readJson(seedPath);',
2015
+ ' const routes = seed && seed.selected && seed.selected.truck_treating_bol_context && seed.selected.truck_treating_bol_context.browser_routes;',
2016
+ ' const route = normalizeRoute(routes && (routes.delivery || routes.detail || routes.list));',
2017
+ ' if (route) return route;',
2018
+ ' }',
2019
+ ' return "/";',
2020
+ '}',
1288
2021
  'function requestReady(url) {',
1289
2022
  ' return new Promise((resolve) => {',
1290
2023
  ' try {',