bulltrackers-module 1.0.732 → 1.0.733
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.
- package/functions/orchestrator/index.js +19 -17
- package/index.js +8 -29
- package/package.json +1 -1
- package/functions/computation-system/WorkflowOrchestrator.js +0 -213
- package/functions/computation-system/config/monitoring_config.js +0 -31
- package/functions/computation-system/config/validation_overrides.js +0 -10
- package/functions/computation-system/context/ContextFactory.js +0 -143
- package/functions/computation-system/context/ManifestBuilder.js +0 -379
- package/functions/computation-system/data/AvailabilityChecker.js +0 -236
- package/functions/computation-system/data/CachedDataLoader.js +0 -325
- package/functions/computation-system/data/DependencyFetcher.js +0 -455
- package/functions/computation-system/executors/MetaExecutor.js +0 -279
- package/functions/computation-system/executors/PriceBatchExecutor.js +0 -108
- package/functions/computation-system/executors/StandardExecutor.js +0 -465
- package/functions/computation-system/helpers/computation_dispatcher.js +0 -750
- package/functions/computation-system/helpers/computation_worker.js +0 -375
- package/functions/computation-system/helpers/monitor.js +0 -64
- package/functions/computation-system/helpers/on_demand_helpers.js +0 -154
- package/functions/computation-system/layers/extractors.js +0 -1097
- package/functions/computation-system/layers/index.js +0 -40
- package/functions/computation-system/layers/mathematics.js +0 -522
- package/functions/computation-system/layers/profiling.js +0 -537
- package/functions/computation-system/layers/validators.js +0 -170
- package/functions/computation-system/legacy/AvailabilityCheckerOld.js +0 -388
- package/functions/computation-system/legacy/CachedDataLoaderOld.js +0 -357
- package/functions/computation-system/legacy/DependencyFetcherOld.js +0 -478
- package/functions/computation-system/legacy/MetaExecutorold.js +0 -364
- package/functions/computation-system/legacy/StandardExecutorold.js +0 -476
- package/functions/computation-system/legacy/computation_dispatcherold.js +0 -944
- package/functions/computation-system/logger/logger.js +0 -297
- package/functions/computation-system/persistence/ContractValidator.js +0 -81
- package/functions/computation-system/persistence/FirestoreUtils.js +0 -56
- package/functions/computation-system/persistence/ResultCommitter.js +0 -283
- package/functions/computation-system/persistence/ResultsValidator.js +0 -130
- package/functions/computation-system/persistence/RunRecorder.js +0 -142
- package/functions/computation-system/persistence/StatusRepository.js +0 -52
- package/functions/computation-system/reporter_epoch.js +0 -6
- package/functions/computation-system/scripts/UpdateContracts.js +0 -128
- package/functions/computation-system/services/SnapshotService.js +0 -148
- package/functions/computation-system/simulation/Fabricator.js +0 -285
- package/functions/computation-system/simulation/SeededRandom.js +0 -41
- package/functions/computation-system/simulation/SimRunner.js +0 -51
- package/functions/computation-system/system_epoch.js +0 -2
- package/functions/computation-system/tools/BuildReporter.js +0 -531
- package/functions/computation-system/tools/ContractDiscoverer.js +0 -144
- package/functions/computation-system/tools/DeploymentValidator.js +0 -536
- package/functions/computation-system/tools/FinalSweepReporter.js +0 -322
- package/functions/computation-system/topology/HashManager.js +0 -55
- package/functions/computation-system/topology/ManifestLoader.js +0 -47
- package/functions/computation-system/utils/data_loader.js +0 -675
- package/functions/computation-system/utils/schema_capture.js +0 -121
- package/functions/computation-system/utils/utils.js +0 -188
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FILENAME: computation-system/helpers/computation_worker.js
|
|
3
|
-
* UPDATED: Implements Google Cloud Monitoring Heartbeats.
|
|
4
|
-
* UPDATED: Implements Structured Logging with Trace Context.
|
|
5
|
-
* FIXED: 'manifest' scope issue (declared outside try/catch).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { executeDispatchTask } = require('../WorkflowOrchestrator.js');
|
|
9
|
-
const { getManifest } = require('../topology/ManifestLoader');
|
|
10
|
-
const { StructuredLogger } = require('../logger/logger');
|
|
11
|
-
const { recordRunAttempt } = require('../persistence/RunRecorder');
|
|
12
|
-
const { normalizeName } = require('../utils/utils');
|
|
13
|
-
const os = require('os');
|
|
14
|
-
const monitoring = require('@google-cloud/monitoring'); // [NEW]
|
|
15
|
-
const monConfig = require('../config/monitoring_config'); // [NEW]
|
|
16
|
-
|
|
17
|
-
// Initialize the Google Monitoring Client once
|
|
18
|
-
const metricClient = new monitoring.MetricServiceClient();
|
|
19
|
-
|
|
20
|
-
let calculationPackage;
|
|
21
|
-
try { calculationPackage = require('aiden-shared-calculations-unified'); } catch (e) { throw e; }
|
|
22
|
-
const calculations = calculationPackage.calculations;
|
|
23
|
-
|
|
24
|
-
const MAX_RETRIES = 3;
|
|
25
|
-
const STALE_LOCK_THRESHOLD_MS = 1000 * 60 * 15; // 15 minutes
|
|
26
|
-
|
|
27
|
-
// =============================================================================
|
|
28
|
-
// HELPER: Firestore Timestamp Conversion
|
|
29
|
-
// =============================================================================
|
|
30
|
-
/**
|
|
31
|
-
* Converts a Firestore Timestamp or Date to milliseconds.
|
|
32
|
-
* Firestore stores Date objects as Timestamp objects, which have a .toDate() method.
|
|
33
|
-
* This function handles both cases correctly.
|
|
34
|
-
* @param {any} field - Firestore Timestamp, Date object, or string
|
|
35
|
-
* @returns {number} Milliseconds since epoch, or 0 if invalid
|
|
36
|
-
*/
|
|
37
|
-
function getMillis(field) {
|
|
38
|
-
if (!field) return 0;
|
|
39
|
-
// Handle Firestore Timestamp (has .toDate() method)
|
|
40
|
-
if (field.toDate && typeof field.toDate === 'function') {
|
|
41
|
-
return field.toDate().getTime();
|
|
42
|
-
}
|
|
43
|
-
// Handle standard Date object or string
|
|
44
|
-
const date = new Date(field);
|
|
45
|
-
return isNaN(date.getTime()) ? 0 : date.getTime();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// [NEW] Helper to push metric to Google Cloud
|
|
49
|
-
async function pushMetric(type, value, labels) {
|
|
50
|
-
if (!monConfig.enabled) return;
|
|
51
|
-
try {
|
|
52
|
-
const dataPoint = {
|
|
53
|
-
interval: { endTime: { seconds: Date.now() / 1000 } },
|
|
54
|
-
value: { int64Value: value }, // Assumes memory in MB
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const timeSeriesData = {
|
|
58
|
-
name: metricClient.projectPath(monConfig.project),
|
|
59
|
-
timeSeries: [{
|
|
60
|
-
metric: {
|
|
61
|
-
type: type,
|
|
62
|
-
labels: labels,
|
|
63
|
-
},
|
|
64
|
-
resource: {
|
|
65
|
-
type: 'global',
|
|
66
|
-
labels: { project_id: monConfig.project },
|
|
67
|
-
},
|
|
68
|
-
points: [dataPoint],
|
|
69
|
-
}],
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// Fire and forget (don't await to avoid blocking compute)
|
|
73
|
-
metricClient.createTimeSeries(timeSeriesData).catch(err => console.error('[Monitoring] Push failed', err.message));
|
|
74
|
-
} catch (e) { /* Ignore setup errors */ }
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function startMemoryHeartbeat(db, ledgerPath, workerId, computationName, traceId, intervalMs = 5000) {
|
|
78
|
-
let peakRss = 0;
|
|
79
|
-
|
|
80
|
-
// Firestore Heartbeat (Legacy/State)
|
|
81
|
-
const firestoreTimer = setInterval(async () => {
|
|
82
|
-
const rssMB = Math.round(process.memoryUsage().rss / 1024 / 1024);
|
|
83
|
-
if (rssMB > peakRss) peakRss = rssMB;
|
|
84
|
-
await db.doc(ledgerPath).update({
|
|
85
|
-
'telemetry.lastMemoryMB': rssMB,
|
|
86
|
-
'telemetry.lastHeartbeat': new Date()
|
|
87
|
-
}).catch(() => {});
|
|
88
|
-
}, 2000); // 2s for DB
|
|
89
|
-
|
|
90
|
-
// Google Monitoring Heartbeat (New/Out-of-Band)
|
|
91
|
-
const monitoringTimer = setInterval(async () => {
|
|
92
|
-
const rssMB = Math.round(process.memoryUsage().rss / 1024 / 1024);
|
|
93
|
-
|
|
94
|
-
// Push Memory Metric
|
|
95
|
-
pushMetric(monConfig.metrics.memory, rssMB, {
|
|
96
|
-
worker_id: workerId,
|
|
97
|
-
computation: computationName,
|
|
98
|
-
// Including trace_id in metric labels allows strict correlation
|
|
99
|
-
// BUT can cause high cardinality. Use with caution or only for debugging.
|
|
100
|
-
// Per user request, we include it to "attach eyeball".
|
|
101
|
-
trace_id: traceId || 'unknown'
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
}, intervalMs); // 5s for API
|
|
105
|
-
|
|
106
|
-
firestoreTimer.unref();
|
|
107
|
-
monitoringTimer.unref();
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
stop: () => { clearInterval(firestoreTimer); clearInterval(monitoringTimer); },
|
|
111
|
-
getPeak: () => peakRss
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* STRICT IDEMPOTENCY GATE
|
|
117
|
-
*/
|
|
118
|
-
async function checkIdempotencyAndClaimLease(db, ledgerPath, dispatchId, workerId, onDemand = false) {
|
|
119
|
-
const docRef = db.doc(ledgerPath);
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
return await db.runTransaction(async (t) => {
|
|
123
|
-
const doc = await t.get(docRef);
|
|
124
|
-
|
|
125
|
-
if (doc.exists) {
|
|
126
|
-
const data = doc.data();
|
|
127
|
-
|
|
128
|
-
// [ON-DEMAND OVERRIDE] If this is an on-demand request, force re-run even if completed
|
|
129
|
-
if (onDemand && ['COMPLETED', 'FAILED', 'CRASH'].includes(data.status)) {
|
|
130
|
-
// Force overwrite - on-demand requests should always run
|
|
131
|
-
// Log will be handled by caller
|
|
132
|
-
// Continue to claim lease below
|
|
133
|
-
} else {
|
|
134
|
-
// [FIX] Only block if it's the EXACT SAME Dispatch ID (Duplicate Delivery)
|
|
135
|
-
if (['COMPLETED', 'FAILED', 'CRASH'].includes(data.status)) {
|
|
136
|
-
if (data.dispatchId === dispatchId) {
|
|
137
|
-
return { shouldRun: false, reason: `Task already in terminal state: ${data.status}` };
|
|
138
|
-
}
|
|
139
|
-
// If dispatchId differs, we allow the overwrite (Re-Run).
|
|
140
|
-
// The Dispatcher is the authority; if it sent a message, we run it.
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (data.status === 'IN_PROGRESS' && data.dispatchId === dispatchId) {
|
|
145
|
-
return { shouldRun: false, reason: 'Duplicate delivery: Task already IN_PROGRESS with same ID.' };
|
|
146
|
-
}
|
|
147
|
-
if (data.status === 'IN_PROGRESS' && !onDemand) {
|
|
148
|
-
// CRITICAL FIX: Check if the lock is stale before blocking
|
|
149
|
-
// If the task has been IN_PROGRESS for more than 15 minutes, break the lock
|
|
150
|
-
const lastActivityTime = getMillis(data.telemetry?.lastHeartbeat) || getMillis(data.startedAt);
|
|
151
|
-
const lockAge = Date.now() - lastActivityTime;
|
|
152
|
-
|
|
153
|
-
if (lockAge > STALE_LOCK_THRESHOLD_MS) {
|
|
154
|
-
// Stale lock detected - break it and allow this worker to claim it
|
|
155
|
-
// The transaction will overwrite the stale lock below
|
|
156
|
-
// Note: We can't log here because we're in a transaction, but the caller will log
|
|
157
|
-
// Continue to claim lease below
|
|
158
|
-
} else {
|
|
159
|
-
// Valid active lock - wait for it to complete
|
|
160
|
-
return { shouldRun: false, reason: 'Collision: Task currently IN_PROGRESS by another worker.' };
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const lease = {
|
|
166
|
-
status: 'IN_PROGRESS',
|
|
167
|
-
workerId: workerId,
|
|
168
|
-
dispatchId: dispatchId || 'unknown',
|
|
169
|
-
startedAt: new Date(),
|
|
170
|
-
onDemand: onDemand || false
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
t.set(docRef, lease, { merge: true });
|
|
174
|
-
return { shouldRun: true, leaseData: lease };
|
|
175
|
-
});
|
|
176
|
-
} catch (e) {
|
|
177
|
-
return { shouldRun: false, reason: `Transaction Error: ${e.message}` };
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async function handleComputationTask(message, config, dependencies) {
|
|
182
|
-
// 1. Parse Message
|
|
183
|
-
let data;
|
|
184
|
-
try {
|
|
185
|
-
const raw = message.data?.message?.data || message.data || message.json;
|
|
186
|
-
data = (typeof raw === 'string') ? JSON.parse(Buffer.from(raw, 'base64').toString()) : raw;
|
|
187
|
-
} catch (e) { return; }
|
|
188
|
-
|
|
189
|
-
if (!data || data.action !== 'RUN_COMPUTATION_DATE') return;
|
|
190
|
-
|
|
191
|
-
const { date, pass, computation, previousCategory, triggerReason, dispatchId, dependencyResultHashes, resources, traceContext, metadata } = data;
|
|
192
|
-
const resourceTier = resources || 'standard';
|
|
193
|
-
const onDemand = metadata?.onDemand === true || false; // Extract on-demand flag
|
|
194
|
-
const ledgerPath = `computation_audit_ledger/${date}/passes/${pass}/tasks/${computation}`;
|
|
195
|
-
const workerId = process.env.K_REVISION || os.hostname();
|
|
196
|
-
|
|
197
|
-
// 2. Initialize Trace-Aware Logger (The "Eyeball")
|
|
198
|
-
const globalMetadata = {};
|
|
199
|
-
if (traceContext && monConfig.enabled) {
|
|
200
|
-
globalMetadata['logging.googleapis.com/trace'] = `projects/${monConfig.project}/traces/${traceContext.traceId}`;
|
|
201
|
-
globalMetadata['logging.googleapis.com/spanId'] = traceContext.spanId;
|
|
202
|
-
globalMetadata['logging.googleapis.com/trace_sampled'] = traceContext.sampled;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const logger = new StructuredLogger({
|
|
206
|
-
minLevel: config.minLevel || 'INFO',
|
|
207
|
-
enableStructured: true,
|
|
208
|
-
globalMetadata,
|
|
209
|
-
...config
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
const runDeps = { ...dependencies, logger };
|
|
213
|
-
const db = dependencies.db;
|
|
214
|
-
|
|
215
|
-
// --- STEP 1: IDEMPOTENCY CHECK (with on-demand override) ---
|
|
216
|
-
// Check for stale locks before the transaction (for logging purposes)
|
|
217
|
-
const preCheckSnap = await db.doc(ledgerPath).get();
|
|
218
|
-
let wasStaleLock = false;
|
|
219
|
-
if (preCheckSnap.exists) {
|
|
220
|
-
const preCheckData = preCheckSnap.data();
|
|
221
|
-
if (preCheckData.status === 'IN_PROGRESS' && preCheckData.workerId !== workerId) {
|
|
222
|
-
const lastActivityTime = getMillis(preCheckData.telemetry?.lastHeartbeat) || getMillis(preCheckData.startedAt);
|
|
223
|
-
const lockAge = Date.now() - lastActivityTime;
|
|
224
|
-
if (lockAge > STALE_LOCK_THRESHOLD_MS) {
|
|
225
|
-
wasStaleLock = true;
|
|
226
|
-
logger.log('WARN', `[Worker] 🧟 Breaking stale lock for ${computation}. Previous worker ${preCheckData.workerId} was inactive for ${Math.round(lockAge / 1000 / 60)} minutes.`);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const gate = await checkIdempotencyAndClaimLease(db, ledgerPath, dispatchId, workerId, onDemand);
|
|
232
|
-
if (!gate.shouldRun) {
|
|
233
|
-
logger.log('WARN', `[Worker] 🛑 Idempotency Gate: Skipping ${computation}. Reason: ${gate.reason}`);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (wasStaleLock) {
|
|
238
|
-
logger.log('INFO', `[Worker] ✅ Successfully claimed lease for ${computation} after breaking stale lock.`);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (onDemand) {
|
|
242
|
-
logger.log('INFO', `[Worker] 🔄 On-demand request: Forcing re-run of ${computation} for ${date}`);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
logger.log('INFO', `[Worker] 📥 Task: ${computation} (${date}) [Tier: ${resourceTier}] [ID: ${dispatchId}]`);
|
|
246
|
-
|
|
247
|
-
// --- STEP 2: START DUAL HEARTBEATS ---
|
|
248
|
-
const heartbeats = startMemoryHeartbeat(db, ledgerPath, workerId, computation, traceContext?.traceId);
|
|
249
|
-
|
|
250
|
-
// [FIX] Declare manifest here so it is accessible in both try and catch blocks
|
|
251
|
-
let manifest;
|
|
252
|
-
const taskStartTime = Date.now(); // Declare outside try/catch for access in error handler
|
|
253
|
-
|
|
254
|
-
try {
|
|
255
|
-
manifest = getManifest(config.activeProductLines || [], calculations, runDeps);
|
|
256
|
-
const startTime = taskStartTime;
|
|
257
|
-
|
|
258
|
-
const result = await executeDispatchTask(
|
|
259
|
-
date, pass, computation, config, runDeps,
|
|
260
|
-
manifest, previousCategory, dependencyResultHashes,
|
|
261
|
-
metadata // Pass metadata (including targetCid) to orchestrator
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
heartbeats.stop();
|
|
265
|
-
const failureReport = result?.updates?.failureReport || [];
|
|
266
|
-
const successUpdates = result?.updates?.successUpdates || {};
|
|
267
|
-
|
|
268
|
-
if (failureReport.length > 0) {
|
|
269
|
-
const reportedError = failureReport[0].error;
|
|
270
|
-
const errorObj = new Error(reportedError.message);
|
|
271
|
-
errorObj.stage = reportedError.stage;
|
|
272
|
-
throw errorObj;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const calcUpdate = successUpdates[normalizeName(computation)] || {};
|
|
276
|
-
const metrics = {
|
|
277
|
-
durationMs: Date.now() - startTime,
|
|
278
|
-
peakMemoryMB: heartbeats.getPeak(),
|
|
279
|
-
io: calcUpdate.metrics?.io,
|
|
280
|
-
storage: calcUpdate.metrics?.storage,
|
|
281
|
-
execution: calcUpdate.metrics?.execution,
|
|
282
|
-
validation: calcUpdate.metrics?.validation,
|
|
283
|
-
composition: calcUpdate.composition
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
await db.doc(ledgerPath).update({ status: 'COMPLETED', completedAt: new Date() });
|
|
287
|
-
await recordRunAttempt(db, { date, computation, pass }, 'SUCCESS', null, metrics, triggerReason, resourceTier);
|
|
288
|
-
|
|
289
|
-
const { notifyComputationComplete, getComputationDisplayName } = require('../../api-v2/helpers/notification_helpers.js');
|
|
290
|
-
// Send notification if this was an on-demand computation
|
|
291
|
-
if (metadata?.onDemand && metadata?.requestId && metadata?.requestingUserCid) {
|
|
292
|
-
try {
|
|
293
|
-
await notifyComputationComplete(
|
|
294
|
-
dependencies.db,
|
|
295
|
-
dependencies.logger,
|
|
296
|
-
metadata.requestingUserCid,
|
|
297
|
-
metadata.requestId,
|
|
298
|
-
computation,
|
|
299
|
-
getComputationDisplayName(computation),
|
|
300
|
-
true,
|
|
301
|
-
null,
|
|
302
|
-
{
|
|
303
|
-
collectionRegistry: dependencies.collectionRegistry,
|
|
304
|
-
config: config,
|
|
305
|
-
notificationType: 'userActionCompletions'
|
|
306
|
-
}
|
|
307
|
-
);
|
|
308
|
-
} catch (notifError) {
|
|
309
|
-
// Non-critical, log and continue
|
|
310
|
-
if (dependencies.logger) {
|
|
311
|
-
dependencies.logger.log('WARN', `[Worker] Failed to send completion notification for ${computation}`, notifError);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
} catch (err) {
|
|
317
|
-
heartbeats.stop();
|
|
318
|
-
|
|
319
|
-
const isDeterministic = ['SHARDING_LIMIT_EXCEEDED', 'QUALITY_CIRCUIT_BREAKER', 'SEMANTIC_GATE'].includes(err.stage);
|
|
320
|
-
|
|
321
|
-
// [FIX] Now we can safely access 'manifest' to find the current hash
|
|
322
|
-
let currentHash = 'unknown';
|
|
323
|
-
if (typeof manifest !== 'undefined' && Array.isArray(manifest)) {
|
|
324
|
-
const item = manifest.find(c => normalizeName(c.name) === normalizeName(computation));
|
|
325
|
-
if (item) currentHash = item.hash;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (isDeterministic || (message.deliveryAttempt || 1) >= MAX_RETRIES) {
|
|
329
|
-
const errorPayload = { message: err.message, stage: err.stage || 'FATAL' };
|
|
330
|
-
|
|
331
|
-
// [UPDATED] We now save 'hash' and 'resourceTier' to the ledger on failure
|
|
332
|
-
await db.doc(ledgerPath).set({
|
|
333
|
-
status: 'FAILED',
|
|
334
|
-
error: errorPayload,
|
|
335
|
-
failedAt: new Date(),
|
|
336
|
-
hash: currentHash, // <--- CRITICAL FOR DISPATCHER DEAD-LETTER CHECK
|
|
337
|
-
resourceTier: resourceTier
|
|
338
|
-
}, { merge: true });
|
|
339
|
-
|
|
340
|
-
await recordRunAttempt(db, { date, computation, pass }, 'FAILURE', { message: err.message, stage: err.stage || 'FATAL' }, { durationMs: Date.now() - taskStartTime, peakMemoryMB: heartbeats.getPeak() }, triggerReason, resourceTier);
|
|
341
|
-
|
|
342
|
-
// Send error notification if this was an on-demand computation
|
|
343
|
-
if (metadata?.onDemand && metadata?.requestId && metadata?.requestingUserCid) {
|
|
344
|
-
try {
|
|
345
|
-
await notifyComputationComplete(
|
|
346
|
-
db,
|
|
347
|
-
dependencies.logger,
|
|
348
|
-
metadata.requestingUserCid,
|
|
349
|
-
metadata.requestId,
|
|
350
|
-
computation,
|
|
351
|
-
getComputationDisplayName(computation),
|
|
352
|
-
false,
|
|
353
|
-
err.message,
|
|
354
|
-
{
|
|
355
|
-
collectionRegistry: dependencies.collectionRegistry,
|
|
356
|
-
config: config,
|
|
357
|
-
notificationType: 'userActionCompletions'
|
|
358
|
-
}
|
|
359
|
-
);
|
|
360
|
-
} catch (notifError) {
|
|
361
|
-
// Non-critical, log and continue
|
|
362
|
-
if (dependencies.logger) {
|
|
363
|
-
dependencies.logger.log('WARN', `[Worker] Failed to send error notification for ${computation}`, notifError);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
throw err;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
module.exports = { handleComputationTask };
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Monitor helper for Cloud Workflows.
|
|
3
|
-
* Checks the state of the Audit Ledger to determine if a pass is complete.
|
|
4
|
-
* This function is stateless and receives dependencies via injection.
|
|
5
|
-
* THIS FILE IS NOW REDUNDANT, LOGIC HAS MOVED TO THE DISPATCHER AND WORKFLOW
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Checks the status of a specific computation pass.
|
|
10
|
-
* @param {object} req - Express request object (query: date, pass).
|
|
11
|
-
* @param {object} res - Express response object.
|
|
12
|
-
* @param {object} dependencies - Contains db (Firestore), logger.
|
|
13
|
-
*/
|
|
14
|
-
async function checkPassStatus(req, res, dependencies) {
|
|
15
|
-
const { db, logger } = dependencies;
|
|
16
|
-
const { date, pass } = req.query;
|
|
17
|
-
|
|
18
|
-
if (!date || !pass) {
|
|
19
|
-
return res.status(400).json({ error: "Missing 'date' or 'pass' query parameters." });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const ledgerPath = `computation_audit_ledger/${date}/passes/${pass}/tasks`;
|
|
23
|
-
logger.log('INFO', `[Monitor] Checking status for ${date} Pass ${pass} at ${ledgerPath}`);
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
const tasksRef = db.collection(ledgerPath);
|
|
27
|
-
|
|
28
|
-
// 1. Check for Active Tasks (Blocking)
|
|
29
|
-
// If anything is PENDING or IN_PROGRESS, the system is still working.
|
|
30
|
-
const runningSnap = await tasksRef.where('status', 'in', ['PENDING', 'IN_PROGRESS']).get();
|
|
31
|
-
|
|
32
|
-
if (!runningSnap.empty) {
|
|
33
|
-
logger.log('INFO', `[Monitor] Pass ${pass} is RUNNING. Active tasks: ${runningSnap.size}`);
|
|
34
|
-
return res.status(200).json({
|
|
35
|
-
state: 'RUNNING',
|
|
36
|
-
activeCount: runningSnap.size
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// 2. Check for Failures (Retry Condition)
|
|
41
|
-
// If nothing is running, we check if anything ended in FAILED state.
|
|
42
|
-
// We consider these "retryable" by re-triggering the dispatcher.
|
|
43
|
-
const failedSnap = await tasksRef.where('status', '==', 'FAILED').get();
|
|
44
|
-
|
|
45
|
-
if (!failedSnap.empty) {
|
|
46
|
-
logger.log('WARN', `[Monitor] Pass ${pass} finished with FAILURES. Count: ${failedSnap.size}`);
|
|
47
|
-
return res.status(200).json({
|
|
48
|
-
state: 'HAS_FAILURES',
|
|
49
|
-
failureCount: failedSnap.size
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 3. Clean Success
|
|
54
|
-
// No running tasks, no failed tasks.
|
|
55
|
-
logger.log('INFO', `[Monitor] Pass ${pass} COMPLETED successfully.`);
|
|
56
|
-
return res.status(200).json({ state: 'SUCCESS' });
|
|
57
|
-
|
|
58
|
-
} catch (error) {
|
|
59
|
-
logger.log('ERROR', `[Monitor] Failed to check status: ${error.message}`);
|
|
60
|
-
return res.status(500).json({ error: error.message });
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
module.exports = { checkPassStatus };
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Helpers for on-demand computation requests
|
|
3
|
-
* Handles dependency chain resolution and ordered triggering
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { getManifest } = require('../topology/ManifestLoader');
|
|
7
|
-
const { normalizeName } = require('../utils/utils');
|
|
8
|
-
|
|
9
|
-
// Import calculations package (matching computation_worker.js pattern)
|
|
10
|
-
let calculationPackage;
|
|
11
|
-
try {
|
|
12
|
-
calculationPackage = require('aiden-shared-calculations-unified');
|
|
13
|
-
} catch (e) {
|
|
14
|
-
throw new Error(`Failed to load calculations package: ${e.message}`);
|
|
15
|
-
}
|
|
16
|
-
const calculations = calculationPackage.calculations;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Resolves all dependencies for a given computation and returns them grouped by pass
|
|
20
|
-
* @param {string} computationName - The target computation name
|
|
21
|
-
* @param {Array} manifest - The computation manifest
|
|
22
|
-
* @returns {Array<{pass: number, computations: Array<string>}>} Dependencies grouped by pass
|
|
23
|
-
*/
|
|
24
|
-
function resolveDependencyChain(computationName, manifest) {
|
|
25
|
-
const manifestMap = new Map(manifest.map(c => [normalizeName(c.name), c]));
|
|
26
|
-
const normalizedTarget = normalizeName(computationName);
|
|
27
|
-
|
|
28
|
-
const targetCalc = manifestMap.get(normalizedTarget);
|
|
29
|
-
if (!targetCalc) {
|
|
30
|
-
throw new Error(`Computation ${computationName} not found in manifest`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Build adjacency list (dependencies map)
|
|
34
|
-
const adjacency = new Map();
|
|
35
|
-
for (const calc of manifest) {
|
|
36
|
-
const normName = normalizeName(calc.name);
|
|
37
|
-
adjacency.set(normName, (calc.dependencies || []).map(d => normalizeName(d)));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Get all dependencies recursively
|
|
41
|
-
const required = new Set([normalizedTarget]);
|
|
42
|
-
const queue = [normalizedTarget];
|
|
43
|
-
|
|
44
|
-
while (queue.length > 0) {
|
|
45
|
-
const calcName = queue.shift();
|
|
46
|
-
const dependencies = adjacency.get(calcName) || [];
|
|
47
|
-
for (const dep of dependencies) {
|
|
48
|
-
if (!required.has(dep)) {
|
|
49
|
-
required.add(dep);
|
|
50
|
-
queue.push(dep);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Group by pass
|
|
56
|
-
const byPass = new Map();
|
|
57
|
-
for (const calcName of required) {
|
|
58
|
-
const calc = manifestMap.get(calcName);
|
|
59
|
-
if (calc) {
|
|
60
|
-
const pass = calc.pass || 1;
|
|
61
|
-
if (!byPass.has(pass)) {
|
|
62
|
-
byPass.set(pass, []);
|
|
63
|
-
}
|
|
64
|
-
byPass.get(pass).push(calc.name); // Use original name, not normalized
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Convert to sorted array
|
|
69
|
-
const passes = Array.from(byPass.entries())
|
|
70
|
-
.sort((a, b) => a[0] - b[0])
|
|
71
|
-
.map(([pass, computations]) => ({ pass, computations }));
|
|
72
|
-
|
|
73
|
-
return passes;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Triggers computations for a given date, handling dependencies in order
|
|
78
|
-
* @param {string} targetComputation - The target computation to run
|
|
79
|
-
* @param {string} date - The date string (YYYY-MM-DD)
|
|
80
|
-
* @param {object} dependencies - Contains pubsub, logger, etc.
|
|
81
|
-
* @param {object} config - Computation system config
|
|
82
|
-
* @param {object} metadata - Additional metadata for the computation request
|
|
83
|
-
* @returns {Promise<Array>} Array of triggered computation messages
|
|
84
|
-
*/
|
|
85
|
-
async function triggerComputationWithDependencies(targetComputation, date, dependencies, config, metadata = {}) {
|
|
86
|
-
const { pubsub, logger } = dependencies;
|
|
87
|
-
// Use on-demand topic for on-demand computation requests
|
|
88
|
-
const computationTopic = config.computationTopicOnDemand ||
|
|
89
|
-
(config.getComputationTopic && config.getComputationTopic(true)) ||
|
|
90
|
-
'computation-tasks-ondemand';
|
|
91
|
-
const topic = pubsub.topic(computationTopic);
|
|
92
|
-
const crypto = require('crypto');
|
|
93
|
-
|
|
94
|
-
// Get manifest to resolve dependencies
|
|
95
|
-
const manifest = getManifest(config.activeProductLines || [], calculations);
|
|
96
|
-
|
|
97
|
-
// Resolve dependency chain
|
|
98
|
-
const dependencyPasses = resolveDependencyChain(targetComputation, manifest);
|
|
99
|
-
|
|
100
|
-
logger.log('INFO', `[On-Demand] Resolved dependency chain for ${targetComputation}:`, {
|
|
101
|
-
totalPasses: dependencyPasses.length,
|
|
102
|
-
passes: dependencyPasses.map(p => `Pass ${p.pass}: ${p.computations.length} computations`)
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const triggeredMessages = [];
|
|
106
|
-
|
|
107
|
-
// Trigger each pass in order
|
|
108
|
-
for (const passGroup of dependencyPasses) {
|
|
109
|
-
const { pass, computations } = passGroup;
|
|
110
|
-
|
|
111
|
-
// Trigger all computations in this pass
|
|
112
|
-
for (const computation of computations) {
|
|
113
|
-
const dispatchId = crypto.randomUUID();
|
|
114
|
-
const isTarget = normalizeName(computation) === normalizeName(targetComputation);
|
|
115
|
-
|
|
116
|
-
const computationMessage = {
|
|
117
|
-
action: 'RUN_COMPUTATION_DATE',
|
|
118
|
-
computation: computation,
|
|
119
|
-
date: date,
|
|
120
|
-
pass: String(pass),
|
|
121
|
-
dispatchId: dispatchId,
|
|
122
|
-
triggerReason: metadata.triggerReason || 'on_demand',
|
|
123
|
-
resources: metadata.resources || 'standard',
|
|
124
|
-
metadata: {
|
|
125
|
-
...metadata,
|
|
126
|
-
onDemand: true,
|
|
127
|
-
isTargetComputation: isTarget,
|
|
128
|
-
targetCid: metadata.targetCid || null // Pass through targetCid for optimization
|
|
129
|
-
},
|
|
130
|
-
traceContext: {
|
|
131
|
-
traceId: crypto.randomBytes(16).toString('hex'),
|
|
132
|
-
spanId: crypto.randomBytes(8).toString('hex'),
|
|
133
|
-
sampled: true
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
await topic.publishMessage({
|
|
138
|
-
data: Buffer.from(JSON.stringify(computationMessage))
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
triggeredMessages.push(computationMessage);
|
|
142
|
-
|
|
143
|
-
logger.log('INFO', `[On-Demand] Triggered ${computation} (Pass ${pass})${isTarget ? ' [TARGET]' : ' [DEPENDENCY]'}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return triggeredMessages;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
module.exports = {
|
|
151
|
-
resolveDependencyChain,
|
|
152
|
-
triggerComputationWithDependencies
|
|
153
|
-
};
|
|
154
|
-
|