bulltrackers-module 1.0.213 → 1.0.214
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,26 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview
|
|
3
|
-
* Dynamic Manifest Builder (
|
|
3
|
+
* Dynamic Manifest Builder (v6 - Merkle Tree Dependency Hashing)
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* * SAFE MODE:
|
|
13
|
-
* If a calculation uses no detectable layer keywords, it defaults to
|
|
14
|
-
* depending on ALL layers to prevent staleness.
|
|
15
|
-
* * FIXED:
|
|
16
|
-
* - Broadened LAYER_TRIGGERS to detect context usage (e.g. 'math.signals')
|
|
17
|
-
* - Updated isHistorical check to look for 'previousComputed'
|
|
5
|
+
* KEY FEATURES:
|
|
6
|
+
* 1. Smart Layer Hashing: Detects used layers (Math, Extractors) to avoid stale helper code.
|
|
7
|
+
* 2. Cascading Invalidation (Merkle Hashing):
|
|
8
|
+
* The final hash of a computation is derived from:
|
|
9
|
+
* [Own Code] + [Layer States] + [Hashes of all Dependencies]
|
|
10
|
+
* * This guarantees that if Calculation A is updated, Calculation B (which depends on A)
|
|
11
|
+
* will automatically generate a new hash, forcing the system to re-run it.
|
|
18
12
|
*/
|
|
19
13
|
|
|
20
14
|
const { generateCodeHash } = require('../utils/utils');
|
|
21
15
|
|
|
22
16
|
// 1. Import Layers directly to generate their "State Hashes"
|
|
23
|
-
// We import them individually to hash them as distinct domains.
|
|
24
17
|
const MathematicsLayer = require('../layers/mathematics');
|
|
25
18
|
const ExtractorsLayer = require('../layers/extractors');
|
|
26
19
|
const ProfilingLayer = require('../layers/profiling');
|
|
@@ -30,10 +23,6 @@ const ValidatorsLayer = require('../layers/validators');
|
|
|
30
23
|
* 1. Layer Hash Generation
|
|
31
24
|
* -------------------------------------------------- */
|
|
32
25
|
|
|
33
|
-
/**
|
|
34
|
-
* Generates a single hash representing the entire state of a layer module.
|
|
35
|
-
* Combines all exported classes/functions/objects into one deterministic string.
|
|
36
|
-
*/
|
|
37
26
|
function generateLayerHash(layerExports, layerName) {
|
|
38
27
|
const keys = Object.keys(layerExports).sort(); // Sort for determinism
|
|
39
28
|
let combinedSource = `LAYER:${layerName}`;
|
|
@@ -60,7 +49,6 @@ const LAYER_HASHES = {
|
|
|
60
49
|
};
|
|
61
50
|
|
|
62
51
|
// Map code patterns to Layer dependencies
|
|
63
|
-
// If a calculation's code contains these strings, it depends on that layer.
|
|
64
52
|
const LAYER_TRIGGERS = {
|
|
65
53
|
'mathematics': [
|
|
66
54
|
'math.compute', 'MathPrimitives',
|
|
@@ -124,18 +112,6 @@ function suggestClosest(name, candidates, n = 3) {
|
|
|
124
112
|
return scores.slice(0, n).map(s => s[0]);
|
|
125
113
|
}
|
|
126
114
|
|
|
127
|
-
function findCycles(manifestMap, adjacencyList) {
|
|
128
|
-
const visited = new Set(), stack = new Set(), cycles = [];
|
|
129
|
-
const dfs = (node, path) => {
|
|
130
|
-
if (stack.has(node)) { const idx = path.indexOf(node); cycles.push([...path.slice(idx), node]); return; }
|
|
131
|
-
if (visited.has(node)) return;
|
|
132
|
-
visited.add(node); stack.add(node);
|
|
133
|
-
for (const nb of adjacencyList.get(node) || []) dfs(nb, [...path, nb]);
|
|
134
|
-
stack.delete(node); };
|
|
135
|
-
for (const name of manifestMap.keys()) dfs(name, [name]);
|
|
136
|
-
return cycles;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
115
|
function getDependencySet(endpoints, adjacencyList) {
|
|
140
116
|
const required = new Set(endpoints);
|
|
141
117
|
const queue = [...endpoints];
|
|
@@ -149,24 +125,21 @@ function getDependencySet(endpoints, adjacencyList) {
|
|
|
149
125
|
* -------------------------------------------------- */
|
|
150
126
|
|
|
151
127
|
function buildManifest(productLinesToRun = [], calculations) {
|
|
152
|
-
log.divider('Building Dynamic Manifest (
|
|
128
|
+
log.divider('Building Dynamic Manifest (Merkle Hashing)');
|
|
153
129
|
log.info(`Target Product Lines: [${productLinesToRun.join(', ')}]`);
|
|
154
|
-
|
|
155
|
-
|
|
130
|
+
|
|
156
131
|
const manifestMap = new Map();
|
|
157
132
|
const adjacency = new Map();
|
|
158
133
|
const reverseAdjacency = new Map();
|
|
159
134
|
const inDegree = new Map();
|
|
160
135
|
let hasFatalError = false;
|
|
161
136
|
|
|
162
|
-
/* ---------------- 1. Load All Calculations ---------------- */
|
|
137
|
+
/* ---------------- 1. Load All Calculations (Phase 1: Intrinsic Hash) ---------------- */
|
|
163
138
|
log.step('Loading and validating all calculation classes…');
|
|
164
|
-
const allCalculationClasses = new Map();
|
|
165
139
|
|
|
166
140
|
function processCalc(Class, name, folderName) {
|
|
167
141
|
if (!Class || typeof Class !== 'function') return;
|
|
168
142
|
const normalizedName = normalizeName(name);
|
|
169
|
-
allCalculationClasses.set(normalizedName, Class);
|
|
170
143
|
|
|
171
144
|
if (typeof Class.getMetadata !== 'function') { log.fatal(`Calculation "${normalizedName}" is missing static getMetadata().`); hasFatalError = true; return; }
|
|
172
145
|
if (typeof Class.getDependencies !== 'function') { log.fatal(`Calculation "${normalizedName}" is missing static getDependencies().`); hasFatalError = true;return; }
|
|
@@ -174,47 +147,34 @@ function buildManifest(productLinesToRun = [], calculations) {
|
|
|
174
147
|
const metadata = Class.getMetadata();
|
|
175
148
|
const dependencies = Class.getDependencies().map(normalizeName);
|
|
176
149
|
|
|
177
|
-
// FIX: Updated check to include 'previousComputed'
|
|
178
150
|
const codeStr = Class.toString();
|
|
179
151
|
if (metadata.isHistorical === true && !codeStr.includes('yesterday') && !codeStr.includes('previousComputed')) {
|
|
180
|
-
log.warn(`Calculation "${normalizedName}" marked 'isHistorical' but no 'previousComputed'
|
|
152
|
+
log.warn(`Calculation "${normalizedName}" marked 'isHistorical' but no 'previousComputed' state reference found.`);
|
|
181
153
|
}
|
|
182
154
|
|
|
183
|
-
let finalCategory = folderName;
|
|
184
|
-
if (folderName === 'core') {
|
|
185
|
-
if (metadata.category) finalCategory = metadata.category;
|
|
186
|
-
} else {
|
|
187
|
-
finalCategory = folderName;
|
|
188
|
-
}
|
|
155
|
+
let finalCategory = folderName === 'core' && metadata.category ? metadata.category : folderName;
|
|
189
156
|
|
|
190
|
-
// ---
|
|
191
|
-
|
|
157
|
+
// --- PHASE 1: INTRINSIC HASH (Code + Layers) ---
|
|
158
|
+
// We do NOT include dependencies yet.
|
|
159
|
+
let compositeHashString = generateCodeHash(codeStr);
|
|
192
160
|
const usedLayers = [];
|
|
193
161
|
|
|
194
|
-
//
|
|
162
|
+
// Check for specific layer usage
|
|
195
163
|
for (const [layerName, triggers] of Object.entries(LAYER_TRIGGERS)) {
|
|
196
|
-
// If code contains any trigger for this layer
|
|
197
164
|
if (triggers.some(trigger => codeStr.includes(trigger))) {
|
|
198
|
-
compositeHashString += LAYER_HASHES[layerName];
|
|
165
|
+
compositeHashString += LAYER_HASHES[layerName];
|
|
199
166
|
usedLayers.push(layerName);
|
|
200
167
|
}
|
|
201
168
|
}
|
|
202
169
|
|
|
203
|
-
//
|
|
204
|
-
// If we found NO specific triggers (e.g. legacy code or unique structure),
|
|
205
|
-
// assume it might use anything. Depend on ALL layers to be safe.
|
|
170
|
+
// Safe Mode Fallback
|
|
206
171
|
let isSafeMode = false;
|
|
207
172
|
if (usedLayers.length === 0) {
|
|
208
173
|
isSafeMode = true;
|
|
209
174
|
Object.values(LAYER_HASHES).forEach(h => compositeHashString += h);
|
|
210
175
|
}
|
|
211
176
|
|
|
212
|
-
|
|
213
|
-
const finalHash = generateCodeHash(compositeHashString);
|
|
214
|
-
|
|
215
|
-
if (isSafeMode) {
|
|
216
|
-
log.warn(`[Hash] ${normalizedName}: No specific dependencies detected. Enforcing SAFE MODE (Linked to ALL Layers).`);
|
|
217
|
-
}
|
|
177
|
+
const baseHash = generateCodeHash(compositeHashString);
|
|
218
178
|
|
|
219
179
|
const manifestEntry = {
|
|
220
180
|
name: normalizedName,
|
|
@@ -227,7 +187,7 @@ function buildManifest(productLinesToRun = [], calculations) {
|
|
|
227
187
|
userType: metadata.userType,
|
|
228
188
|
dependencies: dependencies,
|
|
229
189
|
pass: 0,
|
|
230
|
-
hash:
|
|
190
|
+
hash: baseHash, // Intrinsic Hash (Updated later to include deps)
|
|
231
191
|
debugUsedLayers: isSafeMode ? ['ALL (Safe Mode)'] : usedLayers
|
|
232
192
|
};
|
|
233
193
|
|
|
@@ -241,7 +201,6 @@ function buildManifest(productLinesToRun = [], calculations) {
|
|
|
241
201
|
throw new Error('Manifest build failed: Invalid calculations object.');
|
|
242
202
|
}
|
|
243
203
|
|
|
244
|
-
// Iterate over folders (folderName becomes the default category)
|
|
245
204
|
for (const folderName in calculations) {
|
|
246
205
|
if (folderName === 'legacy') continue;
|
|
247
206
|
const group = calculations[folderName];
|
|
@@ -312,6 +271,31 @@ function buildManifest(productLinesToRun = [], calculations) {
|
|
|
312
271
|
if (sortedManifest.length !== filteredManifestMap.size) {
|
|
313
272
|
throw new Error('Circular dependency detected. Manifest build failed.'); }
|
|
314
273
|
|
|
274
|
+
/* ---------------- 5. Phase 2: Cascading Dependency Hashing ---------------- */
|
|
275
|
+
// Now that we have a topological order (Dependencies come BEFORE Consumers),
|
|
276
|
+
// we can update hashes sequentially.
|
|
277
|
+
log.step('Computing Cascading Merkle Hashes...');
|
|
278
|
+
|
|
279
|
+
for (const entry of sortedManifest) {
|
|
280
|
+
// Start with the intrinsic hash (Code + Layers)
|
|
281
|
+
let dependencySignature = entry.hash;
|
|
282
|
+
|
|
283
|
+
// Append the hashes of all dependencies
|
|
284
|
+
// Since we are iterating in topo order, dependencies are guaranteed to be processed/updated already.
|
|
285
|
+
if (entry.dependencies && entry.dependencies.length > 0) {
|
|
286
|
+
const depHashes = entry.dependencies.map(depName => {
|
|
287
|
+
const depEntry = filteredManifestMap.get(depName);
|
|
288
|
+
if (!depEntry) return ''; // Should not happen given validation
|
|
289
|
+
return depEntry.hash;
|
|
290
|
+
}).join('|'); // Use separator to prevent collisions
|
|
291
|
+
|
|
292
|
+
dependencySignature += `|DEPS:${depHashes}`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Generate the Final Smart Hash
|
|
296
|
+
entry.hash = generateCodeHash(dependencySignature);
|
|
297
|
+
}
|
|
298
|
+
|
|
315
299
|
log.success(`Total passes required: ${maxPass}`);
|
|
316
300
|
return sortedManifest;
|
|
317
301
|
}
|