@tradejs/cli 3.1.29-beta.290 → 3.1.29-beta.292

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.
@@ -35,6 +35,98 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
35
35
  ));
36
36
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
37
37
 
38
+ // src/lib/coreResearch/trace.ts
39
+ async function* readCoreResearchTrace(filePaths) {
40
+ for (const inputPath of filePaths) {
41
+ const filePath = import_node_path.default.resolve(inputPath);
42
+ const input = (0, import_node_fs.createReadStream)(filePath);
43
+ const lines = import_node_readline.default.createInterface({ input, crlfDelay: Infinity });
44
+ let index = -1;
45
+ try {
46
+ for await (const line of lines) {
47
+ index++;
48
+ if (!line.trim()) continue;
49
+ let event;
50
+ try {
51
+ event = JSON.parse(line);
52
+ } catch (error) {
53
+ throw new Error(
54
+ `${filePath}:${index + 1} contains invalid trace JSON: ${String(error)}`
55
+ );
56
+ }
57
+ yield event;
58
+ }
59
+ } finally {
60
+ lines.close();
61
+ input.destroy();
62
+ }
63
+ }
64
+ }
65
+ var import_node_fs, import_node_readline, import_node_path, summarizeCoreResearchTrace;
66
+ var init_trace = __esm({
67
+ "src/lib/coreResearch/trace.ts"() {
68
+ "use strict";
69
+ import_node_fs = require("fs");
70
+ import_node_readline = __toESM(require("readline"));
71
+ import_node_path = __toESM(require("path"));
72
+ summarizeCoreResearchTrace = async (filePaths = []) => {
73
+ const events = /* @__PURE__ */ Object.create(null);
74
+ const skipCounts = /* @__PURE__ */ Object.create(null);
75
+ const exitCodes = /* @__PURE__ */ Object.create(null);
76
+ const policyRejections = {};
77
+ const policyExamples = [];
78
+ for await (const event of readCoreResearchTrace(filePaths)) {
79
+ events[event.event] = (events[event.event] ?? 0) + 1;
80
+ if (event.event === "skip_summary") {
81
+ for (const [reason, count] of Object.entries(event.skipCounts)) {
82
+ skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
83
+ }
84
+ }
85
+ if (event.event === "position_exited" && event.exitCode) {
86
+ exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
87
+ }
88
+ if ("researchEntryDecision" in event && event.researchEntryDecision && !event.researchEntryDecision.allowed) {
89
+ const reason = event.researchEntryDecision.reason;
90
+ policyRejections[reason] = (policyRejections[reason] ?? 0) + 1;
91
+ if (policyExamples.length < 20)
92
+ policyExamples.push({
93
+ timestamp: event.timestamp,
94
+ symbol: event.symbol,
95
+ decision: event.researchEntryDecision
96
+ });
97
+ }
98
+ }
99
+ return {
100
+ events: Object.fromEntries(Object.entries(events).sort()),
101
+ skipCounts: Object.fromEntries(
102
+ Object.entries(skipCounts).sort(
103
+ ([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
104
+ )
105
+ ),
106
+ exitCodes: Object.fromEntries(
107
+ Object.entries(exitCodes).sort(
108
+ ([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
109
+ )
110
+ ),
111
+ ...policyExamples.length ? {
112
+ researchPolicy: {
113
+ rejections: policyRejections,
114
+ examples: policyExamples
115
+ }
116
+ } : {}
117
+ };
118
+ };
119
+ }
120
+ });
121
+
122
+ // src/lib/coreResearch/support.ts
123
+ var init_support = __esm({
124
+ "src/lib/coreResearch/support.ts"() {
125
+ "use strict";
126
+ init_trace();
127
+ }
128
+ });
129
+
38
130
  // src/lib/coreResearch/types.ts
39
131
  var CORE_RESEARCH_SCHEMA;
40
132
  var init_types = __esm({
@@ -48,15 +140,16 @@ var init_types = __esm({
48
140
  function assert(condition, message) {
49
141
  if (!condition) throw new Error(`Invalid core research spec: ${message}`);
50
142
  }
51
- var import_node_crypto, import_node_fs, import_promises, import_backtest, import_node_path, RESEARCH_ID_RE, SHA256_RE, canonicalJson, sha256Text, sha256Json, sha256File, assertFinite, validateCoreResearchSpec, loadCoreResearchSpec, writeJsonAtomic;
143
+ var import_node_crypto, import_node_fs2, import_promises, import_backtest, import_node_path2, RESEARCH_ID_RE, SHA256_RE, canonicalJson, sha256Text, sha256Json, sha256File, assertFinite, validateCoreResearchSpec, loadCoreResearchSpec, writeJsonAtomic;
52
144
  var init_io = __esm({
53
145
  "src/lib/coreResearch/io.ts"() {
54
146
  "use strict";
55
147
  import_node_crypto = require("crypto");
56
- import_node_fs = require("fs");
148
+ import_node_fs2 = require("fs");
57
149
  import_promises = __toESM(require("fs/promises"));
58
150
  import_backtest = require("@tradejs/core/backtest");
59
- import_node_path = __toESM(require("path"));
151
+ import_node_path2 = __toESM(require("path"));
152
+ init_support();
60
153
  init_types();
61
154
  RESEARCH_ID_RE = /^[a-z0-9][a-z0-9._-]{2,120}$/;
62
155
  SHA256_RE = /^[a-f0-9]{64}$/;
@@ -75,10 +168,10 @@ var init_io = __esm({
75
168
  sha256Text = (value) => (0, import_node_crypto.createHash)("sha256").update(value).digest("hex");
76
169
  sha256Json = (value) => sha256Text(canonicalJson(value));
77
170
  sha256File = async (filePath) => {
78
- const hash = (0, import_node_crypto.createHash)("sha256");
79
- const input = (0, import_node_fs.createReadStream)(filePath);
80
- for await (const chunk of input) hash.update(chunk);
81
- return hash.digest("hex");
171
+ const hash2 = (0, import_node_crypto.createHash)("sha256");
172
+ const input = (0, import_node_fs2.createReadStream)(filePath);
173
+ for await (const chunk of input) hash2.update(chunk);
174
+ return hash2.digest("hex");
82
175
  };
83
176
  assertFinite = (value, name) => {
84
177
  assert(typeof value === "number" && Number.isFinite(value), name);
@@ -312,16 +405,32 @@ var init_io = __esm({
312
405
  Boolean(spec.artifacts?.ledgerPath?.trim()),
313
406
  "artifacts.ledgerPath is required"
314
407
  );
408
+ if (spec.support !== void 0) {
409
+ assert(
410
+ spec.support && typeof spec.support === "object",
411
+ "support must be an object"
412
+ );
413
+ for (const [key, value2] of [
414
+ ["minimumEvents", spec.support.minimumEvents],
415
+ ["minimumBlocks", spec.support.minimumBlocks],
416
+ ...spec.support.minimumCommonSetups === void 0 ? [] : [["minimumCommonSetups", spec.support.minimumCommonSetups]]
417
+ ]) {
418
+ assert(
419
+ typeof value2 === "number" && Number.isSafeInteger(value2) && value2 > 0,
420
+ `support.${key} must be a positive integer`
421
+ );
422
+ }
423
+ }
315
424
  return spec;
316
425
  };
317
426
  loadCoreResearchSpec = async (specPath) => {
318
- const resolvedPath = import_node_path.default.resolve(specPath);
427
+ const resolvedPath = import_node_path2.default.resolve(specPath);
319
428
  const parsed = JSON.parse(await import_promises.default.readFile(resolvedPath, "utf8"));
320
429
  const spec = validateCoreResearchSpec(parsed);
321
430
  return { resolvedPath, spec, specSha256: sha256Json(spec) };
322
431
  };
323
432
  writeJsonAtomic = async (filePath, value) => {
324
- await import_promises.default.mkdir(import_node_path.default.dirname(filePath), { recursive: true });
433
+ await import_promises.default.mkdir(import_node_path2.default.dirname(filePath), { recursive: true });
325
434
  const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
326
435
  await import_promises.default.writeFile(temporaryPath, `${canonicalJson(value)}
327
436
  `, "utf8");
@@ -342,30 +451,30 @@ __export(io_exports, {
342
451
  verifyResearchState: () => verifyResearchState,
343
452
  withResearchLock: () => withResearchLock
344
453
  });
345
- var import_promises2, import_node_path2, import_node_os, import_node_crypto2, researchDirectory, readJson, boundResearchPath, withResearchLock, verifyResearchState, verifyCompletedResearch, initializeResearchState, isRetainedDeletion;
454
+ var import_promises2, import_node_path3, import_node_os, import_node_crypto3, researchDirectory, readJson, boundResearchPath, withResearchLock, verifyResearchState, verifyCompletedResearch, initializeResearchState, isRetainedDeletion;
346
455
  var init_io2 = __esm({
347
456
  "src/lib/strategyResearch/io.ts"() {
348
457
  "use strict";
349
458
  import_promises2 = __toESM(require("fs/promises"));
350
- import_node_path2 = __toESM(require("path"));
459
+ import_node_path3 = __toESM(require("path"));
351
460
  import_node_os = __toESM(require("os"));
352
- import_node_crypto2 = require("crypto");
461
+ import_node_crypto3 = require("crypto");
353
462
  init_io();
354
- researchDirectory = (projectRoot, spec) => import_node_path2.default.join(projectRoot, "data/research/strategy", spec.core.researchId);
463
+ researchDirectory = (projectRoot, spec) => import_node_path3.default.join(projectRoot, "data/research/strategy", spec.core.researchId);
355
464
  readJson = async (file) => JSON.parse(await import_promises2.default.readFile(file, "utf8"));
356
465
  boundResearchPath = async (root, relative) => {
357
- const target = import_node_path2.default.resolve(root, relative);
466
+ const target = import_node_path3.default.resolve(root, relative);
358
467
  const realRoot = await import_promises2.default.realpath(root);
359
468
  let ancestor = target;
360
469
  while (true) {
361
470
  try {
362
471
  const real = await import_promises2.default.realpath(ancestor);
363
- if (real !== realRoot && !real.startsWith(`${realRoot}${import_node_path2.default.sep}`))
472
+ if (real !== realRoot && !real.startsWith(`${realRoot}${import_node_path3.default.sep}`))
364
473
  throw new Error(`Research path escapes Project: ${relative}`);
365
474
  break;
366
475
  } catch (error) {
367
476
  if (error.code !== "ENOENT") throw error;
368
- const parent = import_node_path2.default.dirname(ancestor);
477
+ const parent = import_node_path3.default.dirname(ancestor);
369
478
  if (parent === ancestor) throw error;
370
479
  ancestor = parent;
371
480
  }
@@ -377,8 +486,8 @@ var init_io2 = __esm({
377
486
  projectRoot,
378
487
  "data/research/strategy/.lock"
379
488
  );
380
- await import_promises2.default.mkdir(import_node_path2.default.dirname(file), { recursive: true });
381
- const token = (0, import_node_crypto2.randomUUID)();
489
+ await import_promises2.default.mkdir(import_node_path3.default.dirname(file), { recursive: true });
490
+ const token = (0, import_node_crypto3.randomUUID)();
382
491
  const acquire = () => import_promises2.default.writeFile(
383
492
  file,
384
493
  JSON.stringify({ pid: process.pid, host: import_node_os.default.hostname(), token }),
@@ -411,7 +520,7 @@ var init_io2 = __esm({
411
520
  }
412
521
  };
413
522
  verifyResearchState = async (directory, state) => {
414
- const frozenSpec = await readJson(import_node_path2.default.join(directory, "spec.json"));
523
+ const frozenSpec = await readJson(import_node_path3.default.join(directory, "spec.json"));
415
524
  if (sha256Json(frozenSpec) !== state.specSha256)
416
525
  throw new Error("Frozen research spec changed");
417
526
  for (const [name, stage] of Object.entries(state.stages)) {
@@ -430,14 +539,14 @@ var init_io2 = __esm({
430
539
  };
431
540
  verifyCompletedResearch = async (directory, state) => {
432
541
  await verifyResearchState(directory, state);
433
- const manifest = await readJson(import_node_path2.default.join(directory, "manifest.json"));
542
+ const manifest = await readJson(import_node_path3.default.join(directory, "manifest.json"));
434
543
  if (manifest.specSha256 !== state.specSha256)
435
544
  throw new Error("Final manifest spec mismatch");
436
545
  for (const [relative, expected] of Object.entries(manifest.artifacts))
437
546
  if (await sha256File(await boundResearchPath(directory, relative)) !== expected)
438
547
  throw new Error(`Changed final research artifact: ${relative}`);
439
- const inventory = await readJson(import_node_path2.default.join(directory, "retention.json"));
440
- const projectRoot = import_node_path2.default.resolve(directory, "../../../..");
548
+ const inventory = await readJson(import_node_path3.default.join(directory, "retention.json"));
549
+ const projectRoot = import_node_path3.default.resolve(directory, "../../../..");
441
550
  for (const artifact of inventory.research.permanentArtifacts) {
442
551
  const file = await boundResearchPath(projectRoot, artifact.path);
443
552
  if (await sha256File(file) !== artifact.sha256)
@@ -446,7 +555,7 @@ var init_io2 = __esm({
446
555
  };
447
556
  initializeResearchState = async (directory, spec, lineage) => {
448
557
  await import_promises2.default.mkdir(directory, { recursive: true });
449
- const file = import_node_path2.default.join(directory, "state.json");
558
+ const file = import_node_path3.default.join(directory, "state.json");
450
559
  let previous;
451
560
  try {
452
561
  previous = await readJson(file);
@@ -470,19 +579,19 @@ var init_io2 = __esm({
470
579
  status: "running",
471
580
  stages: {}
472
581
  };
473
- await writeJsonAtomic(import_node_path2.default.join(directory, "spec.json"), spec);
582
+ await writeJsonAtomic(import_node_path3.default.join(directory, "spec.json"), spec);
474
583
  await writeJsonAtomic(file, state);
475
584
  return state;
476
585
  };
477
- isRetainedDeletion = async (directory, file, hash) => {
586
+ isRetainedDeletion = async (directory, file, hash2) => {
478
587
  try {
479
- const inventory = await readJson(import_node_path2.default.join(directory, "retention.json"));
588
+ const inventory = await readJson(import_node_path3.default.join(directory, "retention.json"));
480
589
  const index = inventory.entries.findIndex(
481
- (entry) => entry.path === file && entry.sha256 === hash
590
+ (entry) => entry.path === file && entry.sha256 === hash2
482
591
  );
483
592
  if (index < 0) return false;
484
- const receipt = await readJson(import_node_path2.default.join(directory, "retention", `${index}.json`));
485
- return ["applying", "completed"].includes(receipt.status) && receipt.files[0]?.path === file && receipt.files[0]?.sha256 === hash;
593
+ const receipt = await readJson(import_node_path3.default.join(directory, "retention", `${index}.json`));
594
+ return ["applying", "completed"].includes(receipt.status) && receipt.files[0]?.path === file && receipt.files[0]?.sha256 === hash2;
486
595
  } catch (error) {
487
596
  if (error.code === "ENOENT") return false;
488
597
  throw error;
@@ -504,6 +613,7 @@ var import_node_path16 = __toESM(require("path"));
504
613
  init_io();
505
614
 
506
615
  // src/lib/aiPocketSearch/features.ts
616
+ var import_aiFeatures = require("@tradejs/core/aiFeatures");
507
617
  var DAY_MS = 24 * 60 * 60 * 1e3;
508
618
  var DAYS_PER_WEEK = 7;
509
619
  var DAYS_PER_MONTH = 30.4375;
@@ -872,15 +982,25 @@ var addFlattenedFeatures = ({
872
982
  value,
873
983
  segments,
874
984
  maxDepth,
875
- shouldSkipPath = () => false
985
+ shouldSkipPath = () => false,
986
+ onFeatureExcluded
876
987
  }) => {
877
988
  if (!segments.length && !isPlainRecord(value)) {
878
989
  return;
879
990
  }
880
- if (segments.length > maxDepth || isOutcomePath(segments) || shouldSkipPath(segments)) {
991
+ if (segments.length > maxDepth || isOutcomePath(segments)) {
992
+ onFeatureExcluded?.({
993
+ path: segments.join("."),
994
+ classification: segments.length > maxDepth ? "depth-limit" : "derived-policy"
995
+ });
881
996
  return;
882
997
  }
998
+ if (shouldSkipPath(segments)) return;
883
999
  if (Array.isArray(value)) {
1000
+ onFeatureExcluded?.({
1001
+ path: segments.join("."),
1002
+ classification: "unprojected-array"
1003
+ });
884
1004
  return;
885
1005
  }
886
1006
  if (isFeaturePrimitive(value)) {
@@ -902,7 +1022,8 @@ var addFlattenedFeatures = ({
902
1022
  value: child,
903
1023
  segments: [...segments, key],
904
1024
  maxDepth,
905
- shouldSkipPath
1025
+ shouldSkipPath,
1026
+ onFeatureExcluded
906
1027
  });
907
1028
  }
908
1029
  };
@@ -1114,7 +1235,10 @@ var collectAiPocketFeatureSnapshot = ({
1114
1235
  value: source,
1115
1236
  segments: [],
1116
1237
  maxDepth: 8,
1238
+ onFeatureExcluded,
1117
1239
  shouldSkipPath: (segments) => {
1240
+ if (segments.join(".") === "additionalIndicators.baseContext.tickerProfile" || segments.join(".") === "additionalIndicators.tickerProfile" || segments.join(".") === "additionalIndicators.portfolioContext")
1241
+ return true;
1118
1242
  if (isOperatorExcluded(segments.join("."))) {
1119
1243
  return true;
1120
1244
  }
@@ -1132,6 +1256,10 @@ var collectAiPocketFeatureSnapshot = ({
1132
1256
  return true;
1133
1257
  }
1134
1258
  if (featureProfile === "compact" && isCompactFeaturePathSkipped(segments)) {
1259
+ onFeatureExcluded?.({
1260
+ path: segments.join("."),
1261
+ classification: "compact-profile"
1262
+ });
1135
1263
  return true;
1136
1264
  }
1137
1265
  if (featurePolicy !== "causal-stationary") {
@@ -1166,6 +1294,21 @@ var collectAiPocketFeatureSnapshot = ({
1166
1294
  delete features[key];
1167
1295
  }
1168
1296
  }
1297
+ const registered = payload.featureSnapshot;
1298
+ if (registered) {
1299
+ (0, import_aiFeatures.validateAiFeatureSnapshot)(registered);
1300
+ for (const definition of registered.definitions) {
1301
+ const path17 = `registered.${definition.id}`;
1302
+ if (isOperatorExcluded(path17)) continue;
1303
+ const value = registered.values[definition.id];
1304
+ const classification = definition.role === "outcome" || definition.role === "policy" ? "derived-policy" : definition.role !== "input" ? definition.role : featurePolicy === "causal-stationary" && !definition.stationary ? "raw-nonstationary" : value === null || value === "unknown" ? "data-quality" : "eligible";
1305
+ if (classification !== "eligible") {
1306
+ onFeatureExcluded?.({ path: path17, classification });
1307
+ continue;
1308
+ }
1309
+ features[path17] = value;
1310
+ }
1311
+ }
1169
1312
  return { features, featureCoverage };
1170
1313
  };
1171
1314
  var collectAiPocketFeatures = (options) => collectAiPocketFeatureSnapshot(options).features;
@@ -1784,15 +1927,15 @@ var createPocketResult = (rows, predicates, mask, validationRows, testRows, obje
1784
1927
  };
1785
1928
  };
1786
1929
  var hashMask = (mask) => {
1787
- let hash = 2166136261;
1930
+ let hash2 = 2166136261;
1788
1931
  for (let index = 0; index < mask.length; index += 1) {
1789
1932
  if (mask[index] !== 1) {
1790
1933
  continue;
1791
1934
  }
1792
- hash ^= index + 1;
1793
- hash = Math.imul(hash, 16777619);
1935
+ hash2 ^= index + 1;
1936
+ hash2 = Math.imul(hash2, 16777619);
1794
1937
  }
1795
- return (hash >>> 0).toString(36);
1938
+ return (hash2 >>> 0).toString(36);
1796
1939
  };
1797
1940
  var isBetterRepresentativePocket = (candidate, existing, compare) => candidate.depth < existing.depth || candidate.depth === existing.depth && candidate.condition.length < existing.condition.length || candidate.depth === existing.depth && candidate.condition.length === existing.condition.length && compare(candidate, existing) < 0;
1798
1941
  var estimateCombinationCount = (poolSize, maxDepth, maxCombinations) => {
@@ -2489,7 +2632,52 @@ var policyVariant = (spec, coreId, policy, id, jev) => {
2489
2632
 
2490
2633
  // src/lib/strategyResearch/orchestrator.ts
2491
2634
  var import_promises5 = __toESM(require("fs/promises"));
2492
- var import_node_path6 = __toESM(require("path"));
2635
+
2636
+ // src/lib/researchMetricSnapshot.ts
2637
+ var import_node_crypto2 = require("crypto");
2638
+ var import_node_zlib = require("zlib");
2639
+ var MAX_BYTES = 256 * 1024 * 1024;
2640
+ var hash = (bytes) => (0, import_node_crypto2.createHash)("sha256").update(bytes).digest("hex");
2641
+ var validatePermanentResearchMetrics = (value) => {
2642
+ if (!value || typeof value !== "object") return;
2643
+ for (const [key, child] of Object.entries(value)) {
2644
+ if (["matchedPairs", "tradeRows", "allProjectedLeaves"].includes(key) && Array.isArray(child) && child.length)
2645
+ throw new Error(`Temporary payload ${key} is not a permanent metric`);
2646
+ if (key === "trades" && Array.isArray(child) && child.some(
2647
+ (row) => row && typeof row === "object" && ("signalId" in row || "entryTimestamp" in row)
2648
+ ))
2649
+ throw new Error("Temporary trade records are not permanent metrics");
2650
+ validatePermanentResearchMetrics(child);
2651
+ }
2652
+ };
2653
+ var encodeResearchMetricSnapshot = (metrics) => {
2654
+ validatePermanentResearchMetrics(metrics);
2655
+ const bytes = Buffer.from(JSON.stringify(metrics));
2656
+ if (bytes.length > MAX_BYTES)
2657
+ throw new Error("Metric snapshot exceeds decoded size limit");
2658
+ return {
2659
+ schema: "tradejs-metric-snapshot/v1",
2660
+ encoding: "gzip+base64",
2661
+ decodedBytes: bytes.length,
2662
+ sha256: hash(bytes),
2663
+ payload: (0, import_node_zlib.gzipSync)(bytes, { level: 9 }).toString("base64")
2664
+ };
2665
+ };
2666
+ var decodeResearchMetricSnapshot = (snapshot) => {
2667
+ if (!snapshot || typeof snapshot !== "object" || !("schema" in snapshot) || snapshot.schema !== "tradejs-metric-snapshot/v1" || !("encoding" in snapshot) || snapshot.encoding !== "gzip+base64" || !("decodedBytes" in snapshot) || typeof snapshot.decodedBytes !== "number" || !Number.isSafeInteger(snapshot.decodedBytes) || snapshot.decodedBytes <= 0 || snapshot.decodedBytes > MAX_BYTES || !("sha256" in snapshot) || typeof snapshot.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(snapshot.sha256) || !("payload" in snapshot) || typeof snapshot.payload !== "string")
2668
+ throw new Error("Invalid metric snapshot envelope");
2669
+ const bytes = (0, import_node_zlib.gunzipSync)(Buffer.from(snapshot.payload, "base64"), {
2670
+ maxOutputLength: snapshot.decodedBytes
2671
+ });
2672
+ if (bytes.length !== snapshot.decodedBytes || hash(bytes) !== snapshot.sha256)
2673
+ throw new Error("Metric snapshot checksum or size mismatch");
2674
+ const metrics = JSON.parse(bytes.toString("utf8"));
2675
+ validatePermanentResearchMetrics(metrics);
2676
+ return metrics;
2677
+ };
2678
+
2679
+ // src/lib/strategyResearch/orchestrator.ts
2680
+ var import_node_path7 = __toESM(require("path"));
2493
2681
 
2494
2682
  // src/lib/coreResearch/metrics.ts
2495
2683
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
@@ -3690,16 +3878,16 @@ var testCharts = (conclusion) => {
3690
3878
 
3691
3879
  // src/lib/strategyResearch/retention.ts
3692
3880
  var import_promises4 = __toESM(require("fs/promises"));
3693
- var import_node_path5 = __toESM(require("path"));
3881
+ var import_node_path6 = __toESM(require("path"));
3694
3882
 
3695
3883
  // src/lib/strategyRelease/researchRetention.ts
3696
3884
  var import_promises3 = __toESM(require("fs/promises"));
3697
- var import_node_path4 = __toESM(require("path"));
3698
- var import_node_crypto3 = require("crypto");
3885
+ var import_node_path5 = __toESM(require("path"));
3886
+ var import_node_crypto4 = require("crypto");
3699
3887
  var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
3700
3888
 
3701
3889
  // src/lib/strategyRelease/researchFileConsumers.ts
3702
- var import_node_path3 = __toESM(require("path"));
3890
+ var import_node_path4 = __toESM(require("path"));
3703
3891
  var import_node_child_process = require("child_process");
3704
3892
  var import_node_util = require("util");
3705
3893
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
@@ -3730,15 +3918,15 @@ print(json.dumps(sorted(opened)))
3730
3918
  `;
3731
3919
  var assertNoResearchFileConsumers = async (files, run = runCommand) => {
3732
3920
  const virtualPids = /* @__PURE__ */ new Set();
3733
- const wanted = new Set(files.map((file) => import_node_path3.default.resolve(file)));
3734
- let commonRoot = files.length ? import_node_path3.default.dirname(files[0]) : "";
3921
+ const wanted = new Set(files.map((file) => import_node_path4.default.resolve(file)));
3922
+ let commonRoot = files.length ? import_node_path4.default.dirname(files[0]) : "";
3735
3923
  while (commonRoot && files.some((file) => {
3736
- const relative = import_node_path3.default.relative(commonRoot, file);
3737
- return relative === ".." || relative.startsWith(`..${import_node_path3.default.sep}`) || import_node_path3.default.isAbsolute(relative);
3924
+ const relative = import_node_path4.default.relative(commonRoot, file);
3925
+ return relative === ".." || relative.startsWith(`..${import_node_path4.default.sep}`) || import_node_path4.default.isAbsolute(relative);
3738
3926
  }))
3739
- commonRoot = import_node_path3.default.dirname(commonRoot);
3927
+ commonRoot = import_node_path4.default.dirname(commonRoot);
3740
3928
  const selectors = [];
3741
- if (files.length > 1e3 && commonRoot !== import_node_path3.default.parse(commonRoot).root) {
3929
+ if (files.length > 1e3 && commonRoot !== import_node_path4.default.parse(commonRoot).root) {
3742
3930
  selectors.push(["+D", commonRoot]);
3743
3931
  } else {
3744
3932
  for (let offset = 0; offset < files.length; offset += 100)
@@ -3762,7 +3950,7 @@ var assertNoResearchFileConsumers = async (files, run = runCommand) => {
3762
3950
  if (line.startsWith("c"))
3763
3951
  virtual = line.startsWith("ccom.apple.Virtualization.");
3764
3952
  if (line.startsWith("n")) {
3765
- if (!wanted.has(import_node_path3.default.resolve(line.slice(1)))) continue;
3953
+ if (!wanted.has(import_node_path4.default.resolve(line.slice(1)))) continue;
3766
3954
  if (!virtual || !/^\d+$/.test(pid))
3767
3955
  throw new Error(
3768
3956
  `Research cleanup found open files: ${line.slice(1)}`
@@ -3796,9 +3984,9 @@ var assertNoResearchFileConsumers = async (files, run = runCommand) => {
3796
3984
  for (const mount of container.Mounts) {
3797
3985
  if (mount.Type !== "bind") continue;
3798
3986
  for (const file of files) {
3799
- const relative = import_node_path3.default.relative(mount.Source, file);
3800
- if (relative === "" || !relative.startsWith(`..${import_node_path3.default.sep}`) && relative !== ".." && !import_node_path3.default.isAbsolute(relative)) {
3801
- guestFiles.add(import_node_path3.default.posix.join(mount.Destination, relative));
3987
+ const relative = import_node_path4.default.relative(mount.Source, file);
3988
+ if (relative === "" || !relative.startsWith(`..${import_node_path4.default.sep}`) && relative !== ".." && !import_node_path4.default.isAbsolute(relative)) {
3989
+ guestFiles.add(import_node_path4.default.posix.join(mount.Destination, relative));
3802
3990
  covered.add(file);
3803
3991
  }
3804
3992
  }
@@ -3844,15 +4032,15 @@ var mapFiles = async (entries, visit) => {
3844
4032
  if (worker.status === "rejected") throw worker.reason;
3845
4033
  return results;
3846
4034
  };
3847
- var fileSha256 = async (file, bytes) => bytes <= 1024 * 1024 ? (0, import_node_crypto3.createHash)("sha256").update(await import_promises3.default.readFile(file)).digest("hex") : (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(file);
4035
+ var fileSha256 = async (file, bytes) => bytes <= 1024 * 1024 ? (0, import_node_crypto4.createHash)("sha256").update(await import_promises3.default.readFile(file)).digest("hex") : (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(file);
3848
4036
  var inside = (root, file) => {
3849
- const relative = import_node_path4.default.relative(root, file);
3850
- return relative !== "" && !relative.startsWith(`..${import_node_path4.default.sep}`) && relative !== ".." && !import_node_path4.default.isAbsolute(relative);
4037
+ const relative = import_node_path5.default.relative(root, file);
4038
+ return relative !== "" && !relative.startsWith(`..${import_node_path5.default.sep}`) && relative !== ".." && !import_node_path5.default.isAbsolute(relative);
3851
4039
  };
3852
4040
  var applyResearchRetention = async (options) => {
3853
4041
  const projectRoot = await import_promises3.default.realpath(options.projectRoot);
3854
- const dataRoot = import_node_path4.default.join(projectRoot, "data");
3855
- const resolve = (file) => import_node_path4.default.resolve(projectRoot, file);
4042
+ const dataRoot = import_node_path5.default.join(projectRoot, "data");
4043
+ const resolve = (file) => import_node_path5.default.resolve(projectRoot, file);
3856
4044
  const receiptPath = resolve(options.receiptPath);
3857
4045
  const journalPath = `${receiptPath}.deleted.jsonl`;
3858
4046
  const permanent = new Set(
@@ -3888,13 +4076,27 @@ var applyResearchRetention = async (options) => {
3888
4076
  const header = Buffer.alloc(4096);
3889
4077
  const { bytesRead } = await handle.read(header, 0, header.length, 0);
3890
4078
  const frontmatter = header.subarray(0, bytesRead).toString("utf8").match(/^---\r?\n([\s\S]*?)\r?\n---/);
3891
- if (!frontmatter || !/^schema:\s*tradejs-research\/v1\s*$/m.test(frontmatter[1]) || !(retired ? /^reproduction:\s*(?:complete|partial|blocked|legacy-partial)\s*$/m.test(
4079
+ if (!frontmatter || !/^schema:\s*tradejs-research\/v[12]\s*$/m.test(frontmatter[1]) || !(retired ? /^reproduction:\s*(?:complete|partial|blocked|legacy-partial)\s*$/m.test(
3892
4080
  frontmatter[1]
3893
4081
  ) : /^reproduction:\s*complete\s*$/m.test(frontmatter[1]))) {
3894
4082
  throw new Error(
3895
4083
  `Research note must have reproduction: complete: ${file}`
3896
4084
  );
3897
4085
  }
4086
+ if (!retired && /^schema:\s*tradejs-research\/v2\s*$/m.test(frontmatter[1])) {
4087
+ if ((await import_promises3.default.stat(file)).size > 8 * 1024 * 1024)
4088
+ throw new Error("Compact permanent note exceeds 8 MiB");
4089
+ const content = await import_promises3.default.readFile(file, "utf8");
4090
+ const section = content.split("## Metrics snapshot (machine-readable)")[1]?.split("## Reported metrics")[0] ?? "";
4091
+ const blocks = [
4092
+ ...section.matchAll(/```json\r?\n([\s\S]*?)\r?\n```/g)
4093
+ ];
4094
+ if (blocks.length !== 1)
4095
+ throw new Error(
4096
+ "Complete v2 note needs one verified metric snapshot"
4097
+ );
4098
+ decodeResearchMetricSnapshot(JSON.parse(blocks[0][1]));
4099
+ }
3898
4100
  } finally {
3899
4101
  await handle.close();
3900
4102
  }
@@ -3937,8 +4139,8 @@ var applyResearchRetention = async (options) => {
3937
4139
  }))
3938
4140
  };
3939
4141
  if (!options.apply) return receipt;
3940
- await import_promises3.default.mkdir(import_node_path4.default.dirname(receiptPath), { recursive: true });
3941
- if (await import_promises3.default.realpath(import_node_path4.default.dirname(receiptPath)) !== import_node_path4.default.dirname(receiptPath)) {
4142
+ await import_promises3.default.mkdir(import_node_path5.default.dirname(receiptPath), { recursive: true });
4143
+ if (await import_promises3.default.realpath(import_node_path5.default.dirname(receiptPath)) !== import_node_path5.default.dirname(receiptPath)) {
3942
4144
  throw new Error("Cleanup receipt refuses symlinked directories");
3943
4145
  }
3944
4146
  const lock = await import_promises3.default.open(`${receiptPath}.lock`, "wx");
@@ -3984,17 +4186,17 @@ var createResearchRetentionInventory = async (projectRoot, directory, spec, stat
3984
4186
  const permanentArtifacts = [];
3985
4187
  for (const [role, file] of [
3986
4188
  ["note", notePath],
3987
- ["metrics", import_node_path5.default.join(directory, "result.json")],
3988
- ["candidates", import_node_path5.default.join(directory, "03-freeze-gates/result.json")],
3989
- ["candidates", import_node_path5.default.join(directory, "resolved-spec.json")],
3990
- ["ledger", import_node_path5.default.join(directory, "05-freeze-primary/result.json")],
3991
- ["chart", import_node_path5.default.join(directory, "report.html")],
4189
+ ["metrics", import_node_path6.default.join(directory, "result.json")],
4190
+ ["candidates", import_node_path6.default.join(directory, "03-freeze-gates/result.json")],
4191
+ ["candidates", import_node_path6.default.join(directory, "resolved-spec.json")],
4192
+ ["ledger", import_node_path6.default.join(directory, "05-freeze-primary/result.json")],
4193
+ ["chart", import_node_path6.default.join(directory, "report.html")],
3992
4194
  ...[
3993
4195
  "source.patch",
3994
4196
  "source-baseline.tar",
3995
4197
  "source-untracked.json",
3996
4198
  "source-jev-models.json"
3997
- ].map((file2) => ["source", import_node_path5.default.join(directory, file2)])
4199
+ ].map((file2) => ["source", import_node_path6.default.join(directory, file2)])
3998
4200
  ]) {
3999
4201
  try {
4000
4202
  permanentArtifacts.push({
@@ -4008,11 +4210,11 @@ var createResearchRetentionInventory = async (projectRoot, directory, spec, stat
4008
4210
  }
4009
4211
  const files = /* @__PURE__ */ new Map();
4010
4212
  for (const stage of Object.values(state.stages)) {
4011
- for (const [relative, hash] of Object.entries(stage.artifacts)) {
4213
+ for (const [relative, hash2] of Object.entries(stage.artifacts)) {
4012
4214
  const file = await boundResearchPath(directory, relative);
4013
4215
  if (relative.endsWith("/result.json")) {
4014
4216
  const result = await readJson(file);
4015
- if (result?.dataset) files.set(file, hash);
4217
+ if (result?.dataset) files.set(file, hash2);
4016
4218
  }
4017
4219
  if (relative.endsWith("/sources.json") || relative.endsWith("/payloads.json")) {
4018
4220
  const sources = await readJson(file);
@@ -4029,13 +4231,13 @@ var createResearchRetentionInventory = async (projectRoot, directory, spec, stat
4029
4231
  }
4030
4232
  }
4031
4233
  const entries = [];
4032
- for (const [file, hash] of files) {
4234
+ for (const [file, hash2] of files) {
4033
4235
  const stat = await import_promises4.default.stat(file);
4034
4236
  entries.push({
4035
4237
  path: file,
4036
4238
  kind: "research_payload",
4037
4239
  researchId: spec.core.researchId,
4038
- sha256: hash,
4240
+ sha256: hash2,
4039
4241
  bytes: stat.size,
4040
4242
  createdAt: stat.birthtimeMs,
4041
4243
  verified: true,
@@ -4054,13 +4256,13 @@ var createResearchRetentionInventory = async (projectRoot, directory, spec, stat
4054
4256
  };
4055
4257
  var applyRetentionInventory = async (projectRoot, directory) => {
4056
4258
  const inventory = await readJson(
4057
- import_node_path5.default.join(directory, "retention.json")
4259
+ import_node_path6.default.join(directory, "retention.json")
4058
4260
  );
4059
- const receipts = import_node_path5.default.join(directory, "retention");
4261
+ const receipts = import_node_path6.default.join(directory, "retention");
4060
4262
  await import_promises4.default.mkdir(receipts, { recursive: true });
4061
4263
  for (let index = 0; index < inventory.entries.length; index++) {
4062
4264
  const entry = inventory.entries[index];
4063
- const receiptPath = import_node_path5.default.join(receipts, `${index}.json`);
4265
+ const receiptPath = import_node_path6.default.join(receipts, `${index}.json`);
4064
4266
  let receipt;
4065
4267
  try {
4066
4268
  receipt = await readJson(receiptPath);
@@ -4096,13 +4298,13 @@ var applyRetentionInventory = async (projectRoot, directory) => {
4096
4298
  entries: [entry],
4097
4299
  receiptPath,
4098
4300
  apply: true,
4099
- protectedPaths: [import_node_path5.default.join(directory, "retention.json")]
4301
+ protectedPaths: [import_node_path6.default.join(directory, "retention.json")]
4100
4302
  });
4101
4303
  }
4102
4304
  return { status: "completed", files: inventory.entries.length };
4103
4305
  };
4104
4306
  var finishResearchRetention = async (projectRoot, directory) => {
4105
- const statusPath = import_node_path5.default.join(directory, "retention-status.json");
4307
+ const statusPath = import_node_path6.default.join(directory, "retention-status.json");
4106
4308
  try {
4107
4309
  const result = await applyRetentionInventory(projectRoot, directory);
4108
4310
  await writeJsonAtomic(statusPath, result);
@@ -4129,7 +4331,7 @@ var runStrategyResearch = async (parameters) => {
4129
4331
  const directory = researchDirectory(projectRoot, spec);
4130
4332
  const lineage = await backend.lineage();
4131
4333
  const state = await initializeResearchState(directory, spec, lineage);
4132
- const statePath = import_node_path6.default.join(directory, "state.json");
4334
+ const statePath = import_node_path7.default.join(directory, "state.json");
4133
4335
  const elapsedBefore = state.elapsedMs ?? 0;
4134
4336
  const activeStart = Date.now();
4135
4337
  const elapsed = () => elapsedBefore + Date.now() - activeStart;
@@ -4139,7 +4341,7 @@ var runStrategyResearch = async (parameters) => {
4139
4341
  directory,
4140
4342
  state,
4141
4343
  conclusion: await readJson(
4142
- import_node_path6.default.join(directory, "result.json")
4344
+ import_node_path7.default.join(directory, "result.json")
4143
4345
  )
4144
4346
  };
4145
4347
  }
@@ -4149,13 +4351,13 @@ var runStrategyResearch = async (parameters) => {
4149
4351
  return writeJsonAtomic(statePath, state);
4150
4352
  };
4151
4353
  const event = async (stage2, status) => import_promises5.default.appendFile(
4152
- import_node_path6.default.join(directory, "events.jsonl"),
4354
+ import_node_path7.default.join(directory, "events.jsonl"),
4153
4355
  `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), stage: stage2, status, specSha256: state.specSha256 })}
4154
4356
  `
4155
4357
  );
4156
4358
  const stage = async (name, work) => {
4157
4359
  const stageDirectory = await boundResearchPath(directory, name);
4158
- const resultPath = import_node_path6.default.join(stageDirectory, "result.json");
4360
+ const resultPath = import_node_path7.default.join(stageDirectory, "result.json");
4159
4361
  if (state.stages[name]?.status === "completed")
4160
4362
  return readJson(resultPath);
4161
4363
  if (elapsed() > spec.budget.maxMinutes * 6e4)
@@ -4174,7 +4376,7 @@ var runStrategyResearch = async (parameters) => {
4174
4376
  await save();
4175
4377
  await event(name, "started");
4176
4378
  await import_promises5.default.writeFile(
4177
- import_node_path6.default.join(directory, "report.html"),
4379
+ import_node_path7.default.join(directory, "report.html"),
4178
4380
  buildStrategyResearchReport({ spec, state, diagnostics })
4179
4381
  );
4180
4382
  try {
@@ -4182,7 +4384,7 @@ var runStrategyResearch = async (parameters) => {
4182
4384
  await writeJsonAtomic(resultPath, result);
4183
4385
  const artifacts = {};
4184
4386
  for (const file of await import_promises5.default.readdir(stageDirectory)) {
4185
- const full = import_node_path6.default.join(stageDirectory, file);
4387
+ const full = import_node_path7.default.join(stageDirectory, file);
4186
4388
  if ((await import_promises5.default.stat(full)).isFile())
4187
4389
  artifacts[`${name}/${file}`] = await sha256File(full);
4188
4390
  }
@@ -4194,7 +4396,7 @@ var runStrategyResearch = async (parameters) => {
4194
4396
  "source-jev-models.json"
4195
4397
  ]) {
4196
4398
  try {
4197
- artifacts[file] = await sha256File(import_node_path6.default.join(directory, file));
4399
+ artifacts[file] = await sha256File(import_node_path7.default.join(directory, file));
4198
4400
  } catch (error) {
4199
4401
  if (error.code !== "ENOENT")
4200
4402
  throw error;
@@ -4227,14 +4429,14 @@ var runStrategyResearch = async (parameters) => {
4227
4429
  let previousRunId;
4228
4430
  try {
4229
4431
  previousRunId = (await readJson(
4230
- import_node_path6.default.join(folder, "native-run.json")
4432
+ import_node_path7.default.join(folder, "native-run.json")
4231
4433
  )).runId;
4232
4434
  } catch (error) {
4233
4435
  if (error.code !== "ENOENT") throw error;
4234
4436
  }
4235
4437
  if (!previousRunId) {
4236
4438
  try {
4237
- if ((await import_promises5.default.stat(import_node_path6.default.join(folder, "backtest.log"))).size)
4439
+ if ((await import_promises5.default.stat(import_node_path7.default.join(folder, "backtest.log"))).size)
4238
4440
  throw new Error(
4239
4441
  "Previous native attempt has no recorded run identity; inspect its log before another launch"
4240
4442
  );
@@ -4255,7 +4457,7 @@ var runStrategyResearch = async (parameters) => {
4255
4457
  },
4256
4458
  variant,
4257
4459
  folder,
4258
- async (runId) => writeJsonAtomic(import_node_path6.default.join(folder, "native-run.json"), { runId }),
4460
+ async (runId) => writeJsonAtomic(import_node_path7.default.join(folder, "native-run.json"), { runId }),
4259
4461
  previousRunId,
4260
4462
  confirmation
4261
4463
  );
@@ -4270,15 +4472,15 @@ var runStrategyResearch = async (parameters) => {
4270
4472
  try {
4271
4473
  const inventory = await stage("01-preflight", async () => {
4272
4474
  const prior = [];
4273
- const root = import_node_path6.default.dirname(directory);
4475
+ const root = import_node_path7.default.dirname(directory);
4274
4476
  for (const id of await import_promises5.default.readdir(root)) {
4275
4477
  if (id === spec.core.researchId || id.startsWith(".")) continue;
4276
4478
  try {
4277
4479
  const previous = await readJson(
4278
- import_node_path6.default.join(root, id, "spec.json")
4480
+ import_node_path7.default.join(root, id, "spec.json")
4279
4481
  );
4280
4482
  const previousState = await readJson(
4281
- import_node_path6.default.join(root, id, "state.json")
4483
+ import_node_path7.default.join(root, id, "state.json")
4282
4484
  );
4283
4485
  const overlap = previous.core.strategy === spec.core.strategy && previous.core.window.start < spec.core.window.end && previous.core.window.end > (spec.testSince || spec.core.window.start);
4284
4486
  if (overlap && previousState.holdoutOpenedAt)
@@ -4304,7 +4506,7 @@ var runStrategyResearch = async (parameters) => {
4304
4506
  let previous;
4305
4507
  try {
4306
4508
  previous = (await readJson(
4307
- import_node_path6.default.join(folder, "native-run.json")
4509
+ import_node_path7.default.join(folder, "native-run.json")
4308
4510
  )).runId;
4309
4511
  } catch (error) {
4310
4512
  if (error.code !== "ENOENT")
@@ -4314,7 +4516,7 @@ var runStrategyResearch = async (parameters) => {
4314
4516
  spec,
4315
4517
  control2,
4316
4518
  folder,
4317
- async (runId) => writeJsonAtomic(import_node_path6.default.join(folder, "native-run.json"), {
4519
+ async (runId) => writeJsonAtomic(import_node_path7.default.join(folder, "native-run.json"), {
4318
4520
  runId
4319
4521
  }),
4320
4522
  previous
@@ -4328,7 +4530,7 @@ var runStrategyResearch = async (parameters) => {
4328
4530
  );
4329
4531
  spec = { ...spec, testSince: partition.testSince };
4330
4532
  }
4331
- await writeJsonAtomic(import_node_path6.default.join(directory, "resolved-spec.json"), spec);
4533
+ await writeJsonAtomic(import_node_path7.default.join(directory, "resolved-spec.json"), spec);
4332
4534
  let gates = [];
4333
4535
  let primaryId = null;
4334
4536
  let frozenSelection;
@@ -4387,10 +4589,10 @@ var runStrategyResearch = async (parameters) => {
4387
4589
  core: { ...spec.core, researchId: spec.frozenFrom }
4388
4590
  });
4389
4591
  const parentState = await readJson(
4390
- import_node_path6.default.join(parentDirectory, "state.json")
4592
+ import_node_path7.default.join(parentDirectory, "state.json")
4391
4593
  );
4392
4594
  const parentSpec = await readJson(
4393
- import_node_path6.default.join(parentDirectory, "spec.json")
4595
+ import_node_path7.default.join(parentDirectory, "spec.json")
4394
4596
  );
4395
4597
  const { verifyCompletedResearch: verifyCompletedResearch2 } = await Promise.resolve().then(() => (init_io2(), io_exports));
4396
4598
  await verifyCompletedResearch2(parentDirectory, parentState);
@@ -4417,10 +4619,10 @@ var runStrategyResearch = async (parameters) => {
4417
4619
  "Frozen revalidation requires complete, unchanged rules/runtime and a strictly new period"
4418
4620
  );
4419
4621
  gates = await readJson(
4420
- import_node_path6.default.join(parentDirectory, "03-freeze-gates/result.json")
4622
+ import_node_path7.default.join(parentDirectory, "03-freeze-gates/result.json")
4421
4623
  );
4422
4624
  frozenSelection = await readJson(
4423
- import_node_path6.default.join(parentDirectory, "05-freeze-primary/result.json")
4625
+ import_node_path7.default.join(parentDirectory, "05-freeze-primary/result.json")
4424
4626
  );
4425
4627
  if (frozenSelection.schema !== "tradejs-research-selection/v1" || !Array.isArray(frozenSelection.compositions))
4426
4628
  throw new Error(
@@ -4549,7 +4751,7 @@ var runStrategyResearch = async (parameters) => {
4549
4751
  `06-raw-metrics-${variant.id}`,
4550
4752
  async (folder) => {
4551
4753
  const resolved = spec.frozenFrom ? (await run(spec, variant, `06-raw-revalidation-${variant.id}`)).variant : (await readJson(
4552
- import_node_path6.default.join(directory, `02-raw-${variant.id}`, "result.json")
4754
+ import_node_path7.default.join(directory, `02-raw-${variant.id}`, "result.json")
4553
4755
  )).variant;
4554
4756
  const dataset = await backend.loadDataset(
4555
4757
  spec,
@@ -4726,7 +4928,7 @@ var runStrategyResearch = async (parameters) => {
4726
4928
  for (const name of Object.keys(state.stages)) {
4727
4929
  try {
4728
4930
  const result = await readJson(
4729
- import_node_path6.default.join(directory, name, "result.json")
4931
+ import_node_path7.default.join(directory, name, "result.json")
4730
4932
  );
4731
4933
  if (result?.variant)
4732
4934
  executions.push({ stage: name, ...result.variant });
@@ -4734,7 +4936,7 @@ var runStrategyResearch = async (parameters) => {
4734
4936
  if (error.code !== "ENOENT") throw error;
4735
4937
  }
4736
4938
  }
4737
- await writeJsonAtomic(import_node_path6.default.join(directory, "result.json"), {
4939
+ await writeJsonAtomic(import_node_path7.default.join(directory, "result.json"), {
4738
4940
  ...conclusion,
4739
4941
  executions,
4740
4942
  rawCore: Object.entries(rawReports).map(([id, windows]) => ({
@@ -4748,7 +4950,7 @@ var runStrategyResearch = async (parameters) => {
4748
4950
  delete state.error;
4749
4951
  await save();
4750
4952
  await import_promises5.default.writeFile(
4751
- import_node_path6.default.join(directory, "report.html"),
4953
+ import_node_path7.default.join(directory, "report.html"),
4752
4954
  buildStrategyResearchReport({
4753
4955
  spec,
4754
4956
  state,
@@ -4779,7 +4981,7 @@ var runStrategyResearch = async (parameters) => {
4779
4981
  )
4780
4982
  };
4781
4983
  const note = `---
4782
- schema: tradejs-research/v1
4984
+ schema: tradejs-research/v2
4783
4985
  strategy: '${spec.core.strategy}'
4784
4986
  date: '${spec.core.createdAt.slice(0, 10)}'
4785
4987
  kind: 'backtest'
@@ -4812,7 +5014,7 @@ ${JSON.stringify(spec, null, 2)}
4812
5014
  ## Metrics snapshot (machine-readable)
4813
5015
 
4814
5016
  \`\`\`json
4815
- ${JSON.stringify(compact, null, 2)}
5017
+ ${JSON.stringify(encodeResearchMetricSnapshot(compact))}
4816
5018
  \`\`\`
4817
5019
 
4818
5020
  ## Reported metrics
@@ -4831,8 +5033,12 @@ ${directory}/report.html
4831
5033
 
4832
5034
  ${conclusion.nextAction} Current-snapshot universe; no deployment authorization. Temporary payloads are deleted only after the permanent record is verified; new trade-level questions require a frozen rerun.
4833
5035
  `;
5036
+ if (Buffer.byteLength(note) > 8 * 1024 * 1024)
5037
+ throw new Error(
5038
+ "Compact permanent note exceeds 8 MiB; archive source separately, never truncate metrics"
5039
+ );
4834
5040
  await import_promises5.default.writeFile(
4835
- import_node_path6.default.join(
5041
+ import_node_path7.default.join(
4836
5042
  noteDirectory,
4837
5043
  `${spec.core.createdAt.slice(0, 10)}-${spec.core.researchId}.md`
4838
5044
  ),
@@ -4841,7 +5047,7 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
4841
5047
  ).catch(async (error) => {
4842
5048
  if (error.code !== "EEXIST") throw error;
4843
5049
  const existing = await import_promises5.default.readFile(
4844
- import_node_path6.default.join(
5050
+ import_node_path7.default.join(
4845
5051
  noteDirectory,
4846
5052
  `${spec.core.createdAt.slice(0, 10)}-${spec.core.researchId}.md`
4847
5053
  ),
@@ -4853,19 +5059,19 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
4853
5059
  );
4854
5060
  });
4855
5061
  await writeJsonAtomic(
4856
- import_node_path6.default.join(directory, "retention.json"),
5062
+ import_node_path7.default.join(directory, "retention.json"),
4857
5063
  await createResearchRetentionInventory(
4858
5064
  projectRoot,
4859
5065
  directory,
4860
5066
  spec,
4861
5067
  state,
4862
- import_node_path6.default.join(
5068
+ import_node_path7.default.join(
4863
5069
  noteDirectory,
4864
5070
  `${spec.core.createdAt.slice(0, 10)}-${spec.core.researchId}.md`
4865
5071
  )
4866
5072
  )
4867
5073
  );
4868
- await writeJsonAtomic(import_node_path6.default.join(directory, "manifest.json"), {
5074
+ await writeJsonAtomic(import_node_path7.default.join(directory, "manifest.json"), {
4869
5075
  schema: "tradejs-strategy-research-manifest/v1",
4870
5076
  specSha256: state.specSha256,
4871
5077
  artifacts: Object.fromEntries(
@@ -4879,7 +5085,7 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
4879
5085
  "retention.json"
4880
5086
  ].map(async (file) => [
4881
5087
  file,
4882
- await sha256File(import_node_path6.default.join(directory, file))
5088
+ await sha256File(import_node_path7.default.join(directory, file))
4883
5089
  ])
4884
5090
  )
4885
5091
  )
@@ -4892,7 +5098,7 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
4892
5098
  );
4893
5099
  }
4894
5100
  parameters.onProgress?.(
4895
- `\u0418\u0442\u043E\u0433: ${conclusion.status}. \u041E\u0442\u0447\u0451\u0442: ${import_node_path6.default.join(directory, "report.html")}`
5101
+ `\u0418\u0442\u043E\u0433: ${conclusion.status}. \u041E\u0442\u0447\u0451\u0442: ${import_node_path7.default.join(directory, "report.html")}`
4896
5102
  );
4897
5103
  return { directory, state, conclusion };
4898
5104
  } catch (error) {
@@ -4913,9 +5119,9 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
4913
5119
  frozenGates: 0
4914
5120
  }
4915
5121
  };
4916
- await writeJsonAtomic(import_node_path6.default.join(directory, "result.json"), conclusion);
5122
+ await writeJsonAtomic(import_node_path7.default.join(directory, "result.json"), conclusion);
4917
5123
  await import_promises5.default.writeFile(
4918
- import_node_path6.default.join(directory, "report.html"),
5124
+ import_node_path7.default.join(directory, "report.html"),
4919
5125
  buildStrategyResearchReport({ spec, state, conclusion, diagnostics })
4920
5126
  );
4921
5127
  throw error;
@@ -4938,10 +5144,10 @@ var import_registry = require("@tradejs/node/registry");
4938
5144
  var import_connectors = require("@tradejs/node/connectors");
4939
5145
 
4940
5146
  // src/lib/coreResearch/dataset.ts
4941
- var import_node_crypto4 = require("crypto");
4942
- var import_node_fs2 = require("fs");
4943
- var import_node_path7 = __toESM(require("path"));
4944
- var import_node_readline = __toESM(require("readline"));
5147
+ var import_node_crypto5 = require("crypto");
5148
+ var import_node_fs3 = require("fs");
5149
+ var import_node_path8 = __toESM(require("path"));
5150
+ var import_node_readline2 = __toESM(require("readline"));
4945
5151
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4946
5152
  var finiteNumber = (value, field) => {
4947
5153
  const numeric = Number(value);
@@ -5151,11 +5357,11 @@ var readCoreResearchVariant = async (variant) => {
5151
5357
  const seen = /* @__PURE__ */ new Map();
5152
5358
  let duplicateRowsDropped = 0;
5153
5359
  for (const inputPath of variant.files) {
5154
- const filePath = import_node_path7.default.resolve(inputPath);
5155
- const input = (0, import_node_fs2.createReadStream)(filePath);
5156
- const hash = (0, import_node_crypto4.createHash)("sha256");
5157
- input.on("data", (chunk) => hash.update(chunk));
5158
- const lines = import_node_readline.default.createInterface({ input, crlfDelay: Infinity });
5360
+ const filePath = import_node_path8.default.resolve(inputPath);
5361
+ const input = (0, import_node_fs3.createReadStream)(filePath);
5362
+ const hash2 = (0, import_node_crypto5.createHash)("sha256");
5363
+ input.on("data", (chunk) => hash2.update(chunk));
5364
+ const lines = import_node_readline2.default.createInterface({ input, crlfDelay: Infinity });
5159
5365
  const fileTrades = [];
5160
5366
  let lineNumber = 0;
5161
5367
  let rows = 0;
@@ -5204,7 +5410,7 @@ var readCoreResearchVariant = async (variant) => {
5204
5410
  trades.push(trade);
5205
5411
  fileTrades.push(trade);
5206
5412
  }
5207
- const fileSha2562 = hash.digest("hex");
5413
+ const fileSha2562 = hash2.digest("hex");
5208
5414
  for (const trade of fileTrades) trade.sourceSha256 = fileSha2562;
5209
5415
  sources.push({
5210
5416
  path: filePath,
@@ -5224,7 +5430,7 @@ var readCoreResearchVariant = async (variant) => {
5224
5430
  };
5225
5431
 
5226
5432
  // src/lib/backtest/checkpoint.ts
5227
- var import_node_crypto5 = require("crypto");
5433
+ var import_node_crypto6 = require("crypto");
5228
5434
  var import_constants = require("@tradejs/core/constants");
5229
5435
  var import_backtest2 = require("@tradejs/core/backtest");
5230
5436
  var import_redis = require("@tradejs/infra/redis");
@@ -5424,6 +5630,7 @@ var extractSignalFromAiDatasetRow = (row) => {
5424
5630
  const { payload } = row;
5425
5631
  return {
5426
5632
  ...payload.signal,
5633
+ aiFeatureReplay: { snapshot: payload.featureSnapshot ?? null },
5427
5634
  strategy: payload.signal.strategy,
5428
5635
  figures: payload.figures ?? {},
5429
5636
  indicators: payload.indicators ?? {},
@@ -5437,9 +5644,9 @@ init_io2();
5437
5644
 
5438
5645
  // src/lib/aiTrainResearch.ts
5439
5646
  var import_node_child_process2 = require("child_process");
5440
- var import_node_crypto7 = require("crypto");
5647
+ var import_node_crypto8 = require("crypto");
5441
5648
  var import_promises7 = __toESM(require("fs/promises"));
5442
- var import_node_path9 = __toESM(require("path"));
5649
+ var import_node_path10 = __toESM(require("path"));
5443
5650
 
5444
5651
  // src/lib/aiTrainMetrics.ts
5445
5652
  var DAY_MS3 = 24 * 60 * 60 * 1e3;
@@ -5449,10 +5656,10 @@ var import_strategies = require("@tradejs/node/strategies");
5449
5656
  var import_types2 = require("@tradejs/types");
5450
5657
 
5451
5658
  // src/lib/strategyGateFingerprint.ts
5452
- var import_node_crypto6 = require("crypto");
5659
+ var import_node_crypto7 = require("crypto");
5453
5660
  var import_node_module = require("module");
5454
5661
  var import_promises6 = __toESM(require("fs/promises"));
5455
- var import_node_path8 = __toESM(require("path"));
5662
+ var import_node_path9 = __toESM(require("path"));
5456
5663
 
5457
5664
  // src/lib/aiTrainResearch.ts
5458
5665
  var DAY_MS4 = 24 * 60 * 60 * 1e3;
@@ -5462,6 +5669,8 @@ var RESEARCH_CONTEXT_ENV_KEYS = [
5462
5669
  "INTERVAL",
5463
5670
  "DERIVATIVES_CONTEXT_ENABLED",
5464
5671
  "DERIVATIVES_CONTEXT_TARGET_ENABLED",
5672
+ "TICKER_PROFILES_PROVIDER",
5673
+ "TICKER_PROFILES_UNIVERSE",
5465
5674
  "DERIVATIVES_CONTEXT_LOOKBACK_HOURS",
5466
5675
  "DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS",
5467
5676
  "DERIVATIVES_CONTEXT_EXCHANGE_PRIORITY",
@@ -5491,7 +5700,7 @@ var normalizeForStableJson = (value) => {
5491
5700
  }
5492
5701
  return value;
5493
5702
  };
5494
- var fingerprintResearchValue = (value) => (0, import_node_crypto7.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
5703
+ var fingerprintResearchValue = (value) => (0, import_node_crypto8.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
5495
5704
  var fingerprintResearchContextEnvironment = (env = process.env) => fingerprintResearchValue(
5496
5705
  Object.fromEntries(
5497
5706
  RESEARCH_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
@@ -5501,81 +5710,7 @@ var fingerprintResearchContextEnvironment = (env = process.env) => fingerprintRe
5501
5710
  // src/lib/strategyResearch/native.ts
5502
5711
  var import_data = require("@tradejs/core/data");
5503
5712
  var import_coreResearch = require("@tradejs/infra/coreResearch");
5504
-
5505
- // src/lib/coreResearch/trace.ts
5506
- var import_node_fs3 = require("fs");
5507
- var import_node_readline2 = __toESM(require("readline"));
5508
- var import_node_path10 = __toESM(require("path"));
5509
- var summarizeCoreResearchTrace = async (filePaths = []) => {
5510
- const events = /* @__PURE__ */ Object.create(null);
5511
- const skipCounts = /* @__PURE__ */ Object.create(null);
5512
- const exitCodes = /* @__PURE__ */ Object.create(null);
5513
- const policyRejections = {};
5514
- const policyExamples = [];
5515
- for (const inputPath of filePaths) {
5516
- const filePath = import_node_path10.default.resolve(inputPath);
5517
- const input = (0, import_node_fs3.createReadStream)(filePath);
5518
- const lines = import_node_readline2.default.createInterface({ input, crlfDelay: Infinity });
5519
- let index = -1;
5520
- try {
5521
- for await (const line of lines) {
5522
- index++;
5523
- if (!line.trim()) continue;
5524
- let event;
5525
- try {
5526
- event = JSON.parse(line);
5527
- } catch (error) {
5528
- throw new Error(
5529
- `${filePath}:${index + 1} contains invalid trace JSON: ${String(error)}`
5530
- );
5531
- }
5532
- events[event.event] = (events[event.event] ?? 0) + 1;
5533
- if (event.event === "skip_summary") {
5534
- for (const [reason, count] of Object.entries(event.skipCounts)) {
5535
- skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
5536
- }
5537
- }
5538
- if (event.event === "position_exited" && event.exitCode) {
5539
- exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
5540
- }
5541
- if ("researchEntryDecision" in event && event.researchEntryDecision && !event.researchEntryDecision.allowed) {
5542
- const reason = event.researchEntryDecision.reason;
5543
- policyRejections[reason] = (policyRejections[reason] ?? 0) + 1;
5544
- if (policyExamples.length < 20)
5545
- policyExamples.push({
5546
- timestamp: event.timestamp,
5547
- symbol: event.symbol,
5548
- decision: event.researchEntryDecision
5549
- });
5550
- }
5551
- }
5552
- } finally {
5553
- lines.close();
5554
- input.destroy();
5555
- }
5556
- }
5557
- return {
5558
- events: Object.fromEntries(Object.entries(events).sort()),
5559
- skipCounts: Object.fromEntries(
5560
- Object.entries(skipCounts).sort(
5561
- ([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
5562
- )
5563
- ),
5564
- exitCodes: Object.fromEntries(
5565
- Object.entries(exitCodes).sort(
5566
- ([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
5567
- )
5568
- ),
5569
- ...policyExamples.length ? {
5570
- researchPolicy: {
5571
- rejections: policyRejections,
5572
- examples: policyExamples
5573
- }
5574
- } : {}
5575
- };
5576
- };
5577
-
5578
- // src/lib/strategyResearch/native.ts
5713
+ init_trace();
5579
5714
  var import_jev4 = require("@tradejs/core/jev");
5580
5715
 
5581
5716
  // src/lib/backtest/jevOptions.ts
@@ -6532,7 +6667,7 @@ init_io();
6532
6667
  var import_promises11 = __toESM(require("fs/promises"));
6533
6668
  var import_node_path15 = __toESM(require("path"));
6534
6669
  var import_node_child_process4 = require("child_process");
6535
- var import_node_crypto9 = require("crypto");
6670
+ var import_node_crypto10 = require("crypto");
6536
6671
  var import_coreResearch2 = require("@tradejs/infra/coreResearch");
6537
6672
 
6538
6673
  // src/lib/coreResearch/artifacts.ts
@@ -6546,9 +6681,10 @@ init_io();
6546
6681
  init_io();
6547
6682
 
6548
6683
  // src/lib/coreResearch/statistics.ts
6549
- var import_node_crypto8 = require("crypto");
6684
+ var import_node_crypto9 = require("crypto");
6550
6685
 
6551
6686
  // src/lib/coreResearch/orchestrator.ts
6687
+ init_trace();
6552
6688
  init_types();
6553
6689
  var createCoreResearchSpecTemplate = (params) => {
6554
6690
  const rootDir = import_node_path15.default.relative(
@@ -6584,7 +6720,7 @@ var createCoreResearchSpecTemplate = (params) => {
6584
6720
  gitSha,
6585
6721
  ...sourceRepository ? { sourceRepository } : {},
6586
6722
  ...dirtyDiff ? {
6587
- dirtyDiffSha256: (0, import_node_crypto9.createHash)("sha256").update(dirtyDiff).digest("hex")
6723
+ dirtyDiffSha256: (0, import_node_crypto10.createHash)("sha256").update(dirtyDiff).digest("hex")
6588
6724
  } : {}
6589
6725
  };
6590
6726
  } catch {