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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|