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 (v5.1 - Smart Hashing with Safe Mode)
3
+ * Dynamic Manifest Builder (v6 - Merkle Tree Dependency Hashing)
4
4
  *
5
- * This script builds the computation manifest and generates a "Smart Hash"
6
- * for each calculation.
7
- *
8
- * KEY FEATURE:
9
- * It performs static analysis on the calculation code to detect which
10
- * specific math layers it utilizes. It then combines the calculation's own
11
- * code hash with the current hashes of those specific layers.
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 (Smart Hashing)');
128
+ log.divider('Building Dynamic Manifest (Merkle Hashing)');
153
129
  log.info(`Target Product Lines: [${productLinesToRun.join(', ')}]`);
154
- log.info(`Layer Hashes Initialized: ${Object.keys(LAYER_HASHES).join(', ')}`);
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' or 'yesterday' reference found.`);
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
- // --- SMART HASH GENERATION ---
191
- let compositeHashString = generateCodeHash(codeStr); // Start with own code
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
- // 1. Check for specific layer usage
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]; // Append Layer Hash
165
+ compositeHashString += LAYER_HASHES[layerName];
199
166
  usedLayers.push(layerName);
200
167
  }
201
168
  }
202
169
 
203
- // 2. SAFE MODE FALLBACK
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
- // Generate final composite hash
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: finalHash, // The Smart 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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.213",
3
+ "version": "1.0.214",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [