bulltrackers-module 1.0.342 → 1.0.343

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,8 +1,8 @@
1
1
  /**
2
2
  * FILENAME: computation-system/helpers/computation_dispatcher.js
3
3
  * PURPOSE: Sequential Cursor-Based Dispatcher.
4
+ * UPDATED: Implemented "Fast-Forward" Scanning Loop to skip empty dates efficiently.
4
5
  * UPDATED: Enforces Strict One-Shot Policy (Standard -> HighMem -> Dead Letter).
5
- * UPDATED: Prevents infinite loops by permanently ignoring deterministic failures.
6
6
  * UPDATED: Generates Google Cloud Trace Context (traceId/spanId) for end-to-end monitoring.
7
7
  */
8
8
 
@@ -12,7 +12,7 @@ const { PubSubUtils } = require('../../core/utils/pubsub_utils');
12
12
  const { fetchComputationStatus } = require('../persistence/StatusRepository');
13
13
  const { checkRootDataAvailability } = require('../data/AvailabilityChecker');
14
14
  const crypto = require('crypto');
15
- const monConfig = require('../config/monitoring_config'); // [NEW]
15
+ const monConfig = require('../config/monitoring_config');
16
16
 
17
17
  const OOM_THRESHOLD_MB = 1500;
18
18
  const BASE_SECONDS_PER_WEIGHT_UNIT = 3;
@@ -281,7 +281,6 @@ async function handleSweepDispatch(config, dependencies, computationManifest, re
281
281
  const currentDispatchId = crypto.randomUUID();
282
282
 
283
283
  const tasksPayload = validTasks.map(t => {
284
- // [NEW] Generate Eyeball Trace
285
284
  const traceId = crypto.randomBytes(16).toString('hex');
286
285
  const spanId = crypto.randomBytes(8).toString('hex');
287
286
 
@@ -294,7 +293,6 @@ async function handleSweepDispatch(config, dependencies, computationManifest, re
294
293
  dispatchId: currentDispatchId,
295
294
  triggerReason: 'SWEEP_RECOVERY',
296
295
  resources: 'high-mem', // FORCE
297
- // [NEW] Attach the eyeball
298
296
  traceContext: {
299
297
  traceId: traceId,
300
298
  spanId: spanId,
@@ -315,7 +313,7 @@ async function handleSweepDispatch(config, dependencies, computationManifest, re
315
313
  }
316
314
 
317
315
  // =============================================================================
318
- // LOGIC: Standard Dispatch (Original)
316
+ // LOGIC: Standard Dispatch (Fast-Forward Enabled)
319
317
  // =============================================================================
320
318
  async function handleStandardDispatch(config, dependencies, computationManifest, reqBody) {
321
319
  const { logger, db } = dependencies;
@@ -336,16 +334,29 @@ async function handleStandardDispatch(config, dependencies, computationManifest,
336
334
  const sessionDates = await getStableDateSession(config, dependencies, passToRun, dateLimitStr, forceRebuild);
337
335
  if (!sessionDates || sessionDates.length === 0) return { status: 'MOVE_TO_NEXT_PASS', dispatched: 0 };
338
336
 
339
- let selectedDate = null;
337
+ // --- Fast-Forward Loop Configuration ---
338
+ // Scans up to 50 dates or 40 seconds to find work, avoiding empty "wait loops"
339
+ const MAX_SCAN_DEPTH = 50;
340
+ const TIME_LIMIT_MS = 40000;
341
+ const startTime = Date.now();
342
+
343
+ let currentCursor = targetCursorN;
340
344
  let selectedTasks = [];
345
+ let selectedDate = null;
346
+ let datesScanned = 0;
341
347
 
342
- if (targetCursorN <= sessionDates.length) {
343
- selectedDate = sessionDates[targetCursorN - 1];
344
- } else {
345
- return { status: 'MOVE_TO_NEXT_PASS', dispatched: 0 };
346
- }
348
+ // Loop until work is found, end is reached, or safety limits hit
349
+ while (currentCursor <= sessionDates.length) {
350
+ datesScanned++;
351
+ selectedDate = sessionDates[currentCursor - 1]; // 0-indexed array
352
+
353
+ // 1. Safety Break (Prevent Timeout)
354
+ if ((Date.now() - startTime) > TIME_LIMIT_MS || datesScanned > MAX_SCAN_DEPTH) {
355
+ logger.log('INFO', `[Dispatcher] ⏩ Fast-forward paused at ${selectedDate} after scanning ${datesScanned} dates.`);
356
+ break;
357
+ }
347
358
 
348
- if (selectedDate) {
359
+ // 2. Analyze Date
349
360
  const earliestDates = await getEarliestDataDates(config, dependencies);
350
361
  let prevDailyStatusPromise = Promise.resolve(null);
351
362
  if (calcsInThisPass.some(c => c.isHistorical)) {
@@ -362,48 +373,70 @@ async function handleStandardDispatch(config, dependencies, computationManifest,
362
373
  checkRootDataAvailability(selectedDate, config, dependencies, DEFINITIVE_EARLIEST_DATES)
363
374
  ]);
364
375
 
365
- if (availability && availability.status) { // Just check availability exists
376
+ if (availability && availability.status) {
366
377
  const report = analyzeDateExecution(selectedDate, calcsInThisPass, availability.status, dailyStatus, manifestMap, prevDailyStatus);
367
378
  let rawTasks = [...report.runnable, ...report.reRuns];
368
379
 
369
380
  if (rawTasks.length > 0) {
370
381
  rawTasks = await attemptSimHashResolution(dependencies, selectedDate, rawTasks, dailyStatus, manifestMap);
371
- selectedTasks = await filterActiveTasks(db, selectedDate, passToRun, rawTasks, logger);
372
- }
373
-
374
- if (selectedTasks.length > 0) {
375
- // Split Logic: Moves OOMs to High-Mem, drops dead letters
376
- const { standard, highMem } = await splitRoutes(db, selectedDate, passToRun, selectedTasks, logger);
377
- selectedTasks = [...standard, ...highMem];
382
+ const activeTasks = await filterActiveTasks(db, selectedDate, passToRun, rawTasks, logger);
383
+
384
+ if (activeTasks.length > 0) {
385
+ const { standard, highMem } = await splitRoutes(db, selectedDate, passToRun, activeTasks, logger);
386
+ selectedTasks = [...standard, ...highMem];
387
+
388
+ if (selectedTasks.length > 0) {
389
+ // Found work! Break loop to dispatch.
390
+ break;
391
+ }
392
+ }
378
393
  }
379
394
  }
395
+
396
+ // No work found for this date. Fast-forward to next.
397
+ currentCursor++;
380
398
  }
381
399
 
400
+ // --- Result Handling ---
401
+
402
+ // Case 1: Satiated (Scanned to end of session with no work)
403
+ if (currentCursor > sessionDates.length && selectedTasks.length === 0) {
404
+ return {
405
+ status: 'CONTINUE_PASS',
406
+ dateProcessed: selectedDate,
407
+ dispatched: 0,
408
+ n_cursor_ignored: false,
409
+ remainingDates: 0,
410
+ nextCursor: currentCursor // Matches length + 1
411
+ };
412
+ }
413
+
414
+ // Case 2: Paused by Limit (No work found yet, but more dates remain)
382
415
  if (selectedTasks.length === 0) {
383
416
  return {
384
417
  status: 'CONTINUE_PASS',
385
418
  dateProcessed: selectedDate,
386
419
  dispatched: 0,
387
420
  n_cursor_ignored: false,
388
- etaSeconds: 0,
389
- remainingDates: sessionDates.length - targetCursorN
421
+ remainingDates: sessionDates.length - currentCursor + 1,
422
+ nextCursor: currentCursor // Resume from here
390
423
  };
391
424
  }
392
425
 
426
+ // Case 3: Work Found (Dispatching)
393
427
  const totalweight = selectedTasks.reduce((sum, t) => sum + (manifestWeightMap.get(normalizeName(t.name)) || 1.0), 0);
394
428
  const currentDispatchId = crypto.randomUUID();
395
429
  const etaSeconds = Math.max(20, Math.ceil(totalweight * BASE_SECONDS_PER_WEIGHT_UNIT));
396
430
 
397
- const taskDetails = selectedTasks.map(t => `${t.name} (${t.reason})`);
398
- logger.log('INFO', `[Dispatcher] Dispatching ${selectedTasks.length} tasks for ${selectedDate}.`, {
399
- date: selectedDate, pass: passToRun, dispatchedCount: selectedTasks.length, etaSeconds, dispatchId: currentDispatchId, details: taskDetails
400
- });
431
+ if (datesScanned > 1) {
432
+ logger.log('INFO', `[Dispatcher] Fast-forwarded ${datesScanned - 1} empty dates. Dispatching ${selectedTasks.length} tasks for ${selectedDate}.`);
433
+ } else {
434
+ logger.log('INFO', `[Dispatcher] ✅ Dispatching ${selectedTasks.length} tasks for ${selectedDate}.`);
435
+ }
401
436
 
402
437
  const mapToTaskPayload = (t) => {
403
- // [NEW] Generate Eyeball Trace
404
438
  const traceId = crypto.randomBytes(16).toString('hex');
405
439
  const spanId = crypto.randomBytes(8).toString('hex');
406
-
407
440
  return {
408
441
  ...t,
409
442
  action: 'RUN_COMPUTATION_DATE',
@@ -413,7 +446,6 @@ async function handleStandardDispatch(config, dependencies, computationManifest,
413
446
  dispatchId: currentDispatchId,
414
447
  triggerReason: t.reason,
415
448
  resources: t.resources || 'standard',
416
- // [NEW] Attach the eyeball
417
449
  traceContext: {
418
450
  traceId: traceId,
419
451
  spanId: spanId,
@@ -448,7 +480,8 @@ async function handleStandardDispatch(config, dependencies, computationManifest,
448
480
  dispatched: selectedTasks.length,
449
481
  n_cursor_ignored: false,
450
482
  etaSeconds: etaSeconds,
451
- remainingDates: sessionDates.length - targetCursorN
483
+ remainingDates: sessionDates.length - targetCursorN,
484
+ nextCursor: currentCursor + 1 // Start next scan AFTER this date
452
485
  };
453
486
  }
454
487
 
@@ -465,32 +498,25 @@ async function splitRoutes(db, date, pass, tasks, logger) {
465
498
  const doc = await db.doc(ledgerPath).get();
466
499
 
467
500
  if (!doc.exists) {
468
- // New task -> Standard
469
501
  standard.push(task);
470
502
  continue;
471
503
  }
472
504
 
473
505
  const data = doc.data();
474
506
 
475
- // If it FAILED, we check if we can escalate it.
476
507
  if (data.status === 'FAILED') {
477
508
  const stage = data.error?.stage;
478
509
 
479
- // 1. QUALITY / LOGIC FAIL: Dead Letter (Drop it)
480
510
  if (['QUALITY_CIRCUIT_BREAKER', 'SEMANTIC_GATE'].includes(stage)) {
481
511
  logger.log('WARN', `[Dispatcher] 🛑 Dropping ${name} - Deterministic Failure (${stage}).`);
482
512
  continue;
483
513
  }
484
514
 
485
- // 2. PREVIOUSLY HIGH MEM FAIL: Dead Letter (Drop it)
486
515
  if (data.resourceTier === 'high-mem') {
487
516
  logger.log('WARN', `[Dispatcher] 🛑 Dropping ${name} - Failed on High-Mem already.`);
488
517
  continue;
489
518
  }
490
519
 
491
- // 3. STANDARD FAIL (Crash/OOM): Promote to High Mem (Retry)
492
- // If it failed standard, we give it ONE shot on high-mem.
493
- // Note: Even if it was an "Unknown" error, we promote to High-Mem to cover OOMs that looked like crashes.
494
520
  highMem.push({
495
521
  ...task,
496
522
  resources: 'high-mem',
@@ -498,7 +524,6 @@ async function splitRoutes(db, date, pass, tasks, logger) {
498
524
  });
499
525
 
500
526
  } else {
501
- // If status is not FAILED (e.g. was Zombie and filterActiveTasks passed it), retry Standard.
502
527
  standard.push(task);
503
528
  }
504
529
  }
@@ -1,6 +1,6 @@
1
1
  # Cloud Workflows: Precision Cursor-Based Orchestrator
2
- # SIMPLE MODE: Dispatch -> Wait ETA -> Next Date
3
- # UPGRADED: Added "Sweep & Verify" Step for High-Mem Recovery
2
+ # FAST-FORWARD MODE: Dispatch -> (Internal Loop) -> Wait ETA -> Jump to Next Valid Cursor
3
+ # UPGRADED: Supports 'nextCursor' to skip empty date ranges instantly.
4
4
 
5
5
  main:
6
6
  params: [input]
@@ -39,12 +39,12 @@ main:
39
39
 
40
40
  - evaluate_dispatch:
41
41
  switch:
42
- # 1. End of Session (Dispatcher reached end of date list)
42
+ # 1. End of Session (Dispatcher reached absolute end of date list)
43
43
  - condition: '${dispatch_res.body.status == "MOVE_TO_NEXT_PASS"}'
44
44
  assign:
45
45
  - pass_complete: true
46
46
 
47
- # 2. Satiation Check (Specific to date/logic)
47
+ # 2. Satiation Check (Dispatcher scanned to end, found nothing)
48
48
  - condition: '${dispatch_res.body.status == "CONTINUE_PASS" and dispatch_res.body.remainingDates == 0 and dispatch_res.body.dispatched == 0}'
49
49
  steps:
50
50
  - log_satiation:
@@ -55,7 +55,7 @@ main:
55
55
  assign:
56
56
  - pass_complete: true
57
57
 
58
- # 3. Work Dispatched: Wait ETA -> Move Next (Ignored flag is FALSE)
58
+ # 3. Work Dispatched: Wait ETA -> Jump to 'nextCursor'
59
59
  - condition: '${dispatch_res.body.dispatched > 0}'
60
60
  steps:
61
61
  - wait_for_completion:
@@ -64,25 +64,26 @@ main:
64
64
  seconds: '${int(dispatch_res.body.etaSeconds)}'
65
65
  - update_cursor:
66
66
  assign:
67
- - n_cursor: '${if(dispatch_res.body.n_cursor_ignored, n_cursor, n_cursor + 1)}'
67
+ # CRITICAL CHANGE: Use dispatcher's specific jump target
68
+ - n_cursor: '${default(dispatch_res.body.nextCursor, n_cursor + 1)}'
68
69
  - next_loop_work:
69
70
  next: sequential_date_loop
70
71
 
71
- # 4. No Work (Clean or Busy): Move Next Immediately
72
+ # 4. No Work (Fast-Forward limit reached or Single Check): Jump to 'nextCursor'
72
73
  - condition: '${dispatch_res.body.dispatched == 0}'
73
74
  steps:
74
75
  - wait_short:
75
76
  call: sys.sleep
76
77
  args:
77
- seconds: 2 # Tiny debounce
78
+ seconds: 2 # Tiny debounce for API limits
78
79
  - update_cursor_retry:
79
80
  assign:
80
- # Dispatcher sends n_cursor_ignored=false, so we increment.
81
- - n_cursor: '${if(dispatch_res.body.n_cursor_ignored, n_cursor, n_cursor + 1)}'
81
+ # CRITICAL CHANGE: Resume exactly where the Fast-Forward loop stopped
82
+ - n_cursor: '${default(dispatch_res.body.nextCursor, n_cursor + 1)}'
82
83
  - next_loop_retry:
83
84
  next: sequential_date_loop
84
85
 
85
- # --- NEW STEP: VERIFICATION & SWEEP ---
86
+ # --- VERIFICATION & SWEEP (No changes needed here) ---
86
87
  - verify_pass_completion:
87
88
  call: http.post
88
89
  args:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.342",
3
+ "version": "1.0.343",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [