bulltrackers-module 1.0.336 → 1.0.338

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.
@@ -6,4 +6,5 @@ module.exports = {
6
6
 
7
7
  "instrument-price-change-1d": { maxZeroPct: 100 }, // Because weekeends/holidays return 0 change, technically crypto means this can't hit 100% but it's usually quite close, so we override
8
8
  "instrument-price-momentum-20d ": { maxZeroPct: 100 }, // Some assets can be very stagnant over a month, especially bonds or stablecoins
9
+ "mimetic-latency": { maxFlatlinePct: 100 },
9
10
  };
@@ -3,6 +3,7 @@
3
3
  * PURPOSE: Sequential Cursor-Based Dispatcher.
4
4
  * BEHAVIOR: Dispatch -> Wait ETA -> Next Date.
5
5
  * UPDATED: Added "Sweep" Protocol for OOM recovery & High-Mem Verification.
6
+ * UPDATED: Added Safety Checks to permanently skip Deterministic Failures.
6
7
  */
7
8
 
8
9
  const { getExpectedDateStrings, getEarliestDataDates, normalizeName, DEFINITIVE_EARLIEST_DATES } = require('../utils/utils.js');
@@ -22,7 +23,7 @@ const STALE_LOCK_THRESHOLD_MS = 1000 * 60 * 15;
22
23
  // =============================================================================
23
24
  async function filterActiveTasks(db, date, pass, tasks, logger, forceRun = false) {
24
25
  if (!tasks || tasks.length === 0) return [];
25
- if (forceRun) return tasks; // Bypass check for Sweep Mode
26
+ if (forceRun) return tasks; // Bypass check for Sweep Mode (Handled separately)
26
27
 
27
28
  const checkPromises = tasks.map(async (t) => {
28
29
  const taskName = normalizeName(t.name);
@@ -54,6 +55,16 @@ async function filterActiveTasks(db, date, pass, tasks, logger, forceRun = false
54
55
  (Date.now() - new Date(data.completedAt).getTime() < 60 * 1000);
55
56
 
56
57
  if (isJustFinished) return null;
58
+
59
+ // 3. DETERMINISTIC FAILURE CHECK (Break Infinite Loops)
60
+ // If the task failed due to Logic/Quality issues, never retry it automatically.
61
+ if (data.status === 'FAILED') {
62
+ const stage = data.error?.stage;
63
+ if (['QUALITY_CIRCUIT_BREAKER', 'SEMANTIC_GATE', 'SHARDING_LIMIT_EXCEEDED'].includes(stage)) {
64
+ if (logger) logger.log('WARN', `[Dispatcher] 🛑 Skipping deterministic failure for ${taskName} (${stage}).`);
65
+ return null;
66
+ }
67
+ }
57
68
  }
58
69
  return t;
59
70
  });
@@ -173,15 +184,12 @@ async function handlePassVerification(config, dependencies, computationManifest,
173
184
 
174
185
  const missingTasks = [];
175
186
 
176
- // Optimize: Batch fetch statuses if possible, but for now loop is safer for memory
177
- // In production, we might want p-limit here.
178
187
  for (const date of sessionDates) {
179
188
  const [dailyStatus, availability] = await Promise.all([
180
189
  fetchComputationStatus(date, config, dependencies),
181
190
  checkRootDataAvailability(date, config, dependencies, DEFINITIVE_EARLIEST_DATES)
182
191
  ]);
183
192
 
184
- // Need previous status for historical calcs
185
193
  let prevDailyStatus = null;
186
194
  if (calcsInPass.some(c => c.isHistorical)) {
187
195
  const prevD = new Date(date + 'T00:00:00Z');
@@ -191,8 +199,6 @@ async function handlePassVerification(config, dependencies, computationManifest,
191
199
 
192
200
  const report = analyzeDateExecution(date, calcsInPass, availability ? availability.status : {}, dailyStatus, manifestMap, prevDailyStatus);
193
201
 
194
- // We only care about Runnable (New) or ReRuns (Changed/Failed)
195
- // We ignore Blocked (impossible to run) and Impossible (permanent fail)
196
202
  const pending = [...report.runnable, ...report.reRuns];
197
203
 
198
204
  if (pending.length > 0) {
@@ -233,7 +239,6 @@ async function handleSweepDispatch(config, dependencies, computationManifest, re
233
239
  checkRootDataAvailability(date, config, dependencies, DEFINITIVE_EARLIEST_DATES)
234
240
  ]);
235
241
 
236
- // Previous Status Fetch (simplified for brevity, assume historical dependency check works or fails safe)
237
242
  let prevDailyStatus = null;
238
243
  if (calcsInPass.some(c => c.isHistorical)) {
239
244
  const prevD = new Date(date + 'T00:00:00Z');
@@ -249,12 +254,33 @@ async function handleSweepDispatch(config, dependencies, computationManifest, re
249
254
  return { dispatched: 0 };
250
255
  }
251
256
 
257
+ // [FIX] Filter out deterministic failures from Sweep.
258
+ // If it failed due to 'QUALITY_CIRCUIT_BREAKER', High-Mem won't fix it.
259
+ const validTasks = [];
260
+ for (const task of pending) {
261
+ const name = normalizeName(task.name);
262
+ const ledgerPath = `computation_audit_ledger/${date}/passes/${passToRun}/tasks/${name}`;
263
+ const doc = await db.doc(ledgerPath).get();
264
+ if (doc.exists) {
265
+ const data = doc.data();
266
+ const stage = data.error?.stage;
267
+ if (['QUALITY_CIRCUIT_BREAKER', 'SEMANTIC_GATE', 'SHARDING_LIMIT_EXCEEDED'].includes(stage)) {
268
+ logger.log('WARN', `[Sweep] 🛑 Skipping deterministic failure for ${name} (${stage}).`);
269
+ continue;
270
+ }
271
+ }
272
+ validTasks.push(task);
273
+ }
274
+
275
+ if (validTasks.length === 0) {
276
+ logger.log('INFO', `[Sweep] ${date} only has deterministic failures. No dispatch.`);
277
+ return { dispatched: 0 };
278
+ }
279
+
252
280
  // 2. FORCE High Mem & Skip Zombie Check
253
- // We do NOT filterActiveTasks because we assume they might be OOM'd zombies
254
- // We do NOT check resource tier from previous runs, we FORCE 'high-mem'
255
281
  const currentDispatchId = crypto.randomUUID();
256
282
 
257
- const tasksPayload = pending.map(t => ({
283
+ const tasksPayload = validTasks.map(t => ({
258
284
  ...t,
259
285
  action: 'RUN_COMPUTATION_DATE',
260
286
  computation: t.name,
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * FILENAME: computation-system/helpers/computation_worker.js
3
+ * UPDATED: Fixed Error Propagation Bug. Preserves 'stage' property when re-throwing logic errors.
3
4
  * UPDATED: Fixed Firestore 'undefined' field error for dispatchId.
4
5
  */
5
6
 
@@ -46,7 +47,7 @@ async function handleComputationTask(message, config, dependencies) {
46
47
 
47
48
  logger.log('INFO', `[Worker] 📥 Task: ${computation} (${date}) [Tier: ${resourceTier}]`);
48
49
 
49
- // [FIX] Build document object and only add dispatchId if it is defined
50
+ // [FIX] Build document object and only add dispatchId if it is defined (prevents Firestore "undefined" error)
50
51
  const leaseData = {
51
52
  status: 'IN_PROGRESS',
52
53
  workerId: process.env.K_REVISION || os.hostname(),
@@ -71,7 +72,16 @@ async function handleComputationTask(message, config, dependencies) {
71
72
  const failureReport = result?.updates?.failureReport || [];
72
73
  const successUpdates = result?.updates?.successUpdates || {};
73
74
 
74
- if (failureReport.length > 0) throw new Error(failureReport[0].error.message);
75
+ // [CRITICAL FIX] Correctly propagate the Error Stage.
76
+ // Previously, 'throw new Error(msg)' stripped the 'stage' property, causing the
77
+ // catch block to treat Deterministic errors (Quality/Logic) as System errors (Transient),
78
+ // triggering infinite Pub/Sub retries.
79
+ if (failureReport.length > 0) {
80
+ const reportedError = failureReport[0].error;
81
+ const errorObj = new Error(reportedError.message);
82
+ errorObj.stage = reportedError.stage; // Preserve stage (e.g. 'QUALITY_CIRCUIT_BREAKER')
83
+ throw errorObj;
84
+ }
75
85
 
76
86
  const calcUpdate = successUpdates[normalizeName(computation)] || {};
77
87
  const metrics = {
@@ -91,11 +101,27 @@ async function handleComputationTask(message, config, dependencies) {
91
101
  clearInterval(heartbeat.timer);
92
102
  const isDeterministic = ['SHARDING_LIMIT_EXCEEDED', 'QUALITY_CIRCUIT_BREAKER', 'SEMANTIC_GATE'].includes(err.stage);
93
103
 
104
+ // If error is deterministic (Logic/Quality), we record FAILURE and RETURN.
105
+ // This ACKs the message and stops the retry loop.
94
106
  if (isDeterministic || (message.deliveryAttempt || 1) >= MAX_RETRIES) {
95
- await db.doc(ledgerPath).set({ status: 'FAILED', error: err.message, failedAt: new Date() }, { merge: true });
107
+
108
+ // Write structured error to Ledger so Dispatcher can see the 'stage' later
109
+ const errorPayload = {
110
+ message: err.message,
111
+ stage: err.stage || 'FATAL'
112
+ };
113
+
114
+ await db.doc(ledgerPath).set({
115
+ status: 'FAILED',
116
+ error: errorPayload,
117
+ failedAt: new Date()
118
+ }, { merge: true });
119
+
96
120
  await recordRunAttempt(db, { date, computation, pass }, 'FAILURE', { message: err.message, stage: err.stage || 'FATAL' }, { peakMemoryMB: heartbeat.getPeak() }, triggerReason, resourceTier);
97
121
  return;
98
122
  }
123
+
124
+ // If non-deterministic (Network/System), throw to trigger Pub/Sub Retry
99
125
  throw err;
100
126
  }
101
127
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.336",
3
+ "version": "1.0.338",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [