@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, 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, 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,10 +405,26 @@ 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
  writeJsonAtomic = async (filePath, value) => {
318
- await import_promises.default.mkdir(import_node_path.default.dirname(filePath), { recursive: true });
427
+ await import_promises.default.mkdir(import_node_path2.default.dirname(filePath), { recursive: true });
319
428
  const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
320
429
  await import_promises.default.writeFile(temporaryPath, `${canonicalJson(value)}
321
430
  `, "utf8");
@@ -336,30 +445,30 @@ __export(io_exports, {
336
445
  verifyResearchState: () => verifyResearchState,
337
446
  withResearchLock: () => withResearchLock
338
447
  });
339
- var import_promises2, import_node_path2, import_node_os, import_node_crypto2, researchDirectory, readJson, boundResearchPath, withResearchLock, verifyResearchState, verifyCompletedResearch, initializeResearchState, isRetainedDeletion;
448
+ var import_promises2, import_node_path3, import_node_os, import_node_crypto3, researchDirectory, readJson, boundResearchPath, withResearchLock, verifyResearchState, verifyCompletedResearch, initializeResearchState, isRetainedDeletion;
340
449
  var init_io2 = __esm({
341
450
  "src/lib/strategyResearch/io.ts"() {
342
451
  "use strict";
343
452
  import_promises2 = __toESM(require("fs/promises"));
344
- import_node_path2 = __toESM(require("path"));
453
+ import_node_path3 = __toESM(require("path"));
345
454
  import_node_os = __toESM(require("os"));
346
- import_node_crypto2 = require("crypto");
455
+ import_node_crypto3 = require("crypto");
347
456
  init_io();
348
- researchDirectory = (projectRoot, spec) => import_node_path2.default.join(projectRoot, "data/research/strategy", spec.core.researchId);
457
+ researchDirectory = (projectRoot, spec) => import_node_path3.default.join(projectRoot, "data/research/strategy", spec.core.researchId);
349
458
  readJson = async (file) => JSON.parse(await import_promises2.default.readFile(file, "utf8"));
350
459
  boundResearchPath = async (root, relative) => {
351
- const target = import_node_path2.default.resolve(root, relative);
460
+ const target = import_node_path3.default.resolve(root, relative);
352
461
  const realRoot = await import_promises2.default.realpath(root);
353
462
  let ancestor = target;
354
463
  while (true) {
355
464
  try {
356
465
  const real = await import_promises2.default.realpath(ancestor);
357
- if (real !== realRoot && !real.startsWith(`${realRoot}${import_node_path2.default.sep}`))
466
+ if (real !== realRoot && !real.startsWith(`${realRoot}${import_node_path3.default.sep}`))
358
467
  throw new Error(`Research path escapes Project: ${relative}`);
359
468
  break;
360
469
  } catch (error) {
361
470
  if (error.code !== "ENOENT") throw error;
362
- const parent = import_node_path2.default.dirname(ancestor);
471
+ const parent = import_node_path3.default.dirname(ancestor);
363
472
  if (parent === ancestor) throw error;
364
473
  ancestor = parent;
365
474
  }
@@ -371,8 +480,8 @@ var init_io2 = __esm({
371
480
  projectRoot,
372
481
  "data/research/strategy/.lock"
373
482
  );
374
- await import_promises2.default.mkdir(import_node_path2.default.dirname(file), { recursive: true });
375
- const token = (0, import_node_crypto2.randomUUID)();
483
+ await import_promises2.default.mkdir(import_node_path3.default.dirname(file), { recursive: true });
484
+ const token = (0, import_node_crypto3.randomUUID)();
376
485
  const acquire = () => import_promises2.default.writeFile(
377
486
  file,
378
487
  JSON.stringify({ pid: process.pid, host: import_node_os.default.hostname(), token }),
@@ -405,7 +514,7 @@ var init_io2 = __esm({
405
514
  }
406
515
  };
407
516
  verifyResearchState = async (directory, state) => {
408
- const frozenSpec = await readJson(import_node_path2.default.join(directory, "spec.json"));
517
+ const frozenSpec = await readJson(import_node_path3.default.join(directory, "spec.json"));
409
518
  if (sha256Json(frozenSpec) !== state.specSha256)
410
519
  throw new Error("Frozen research spec changed");
411
520
  for (const [name, stage] of Object.entries(state.stages)) {
@@ -424,14 +533,14 @@ var init_io2 = __esm({
424
533
  };
425
534
  verifyCompletedResearch = async (directory, state) => {
426
535
  await verifyResearchState(directory, state);
427
- const manifest = await readJson(import_node_path2.default.join(directory, "manifest.json"));
536
+ const manifest = await readJson(import_node_path3.default.join(directory, "manifest.json"));
428
537
  if (manifest.specSha256 !== state.specSha256)
429
538
  throw new Error("Final manifest spec mismatch");
430
539
  for (const [relative, expected] of Object.entries(manifest.artifacts))
431
540
  if (await sha256File(await boundResearchPath(directory, relative)) !== expected)
432
541
  throw new Error(`Changed final research artifact: ${relative}`);
433
- const inventory = await readJson(import_node_path2.default.join(directory, "retention.json"));
434
- const projectRoot = import_node_path2.default.resolve(directory, "../../../..");
542
+ const inventory = await readJson(import_node_path3.default.join(directory, "retention.json"));
543
+ const projectRoot = import_node_path3.default.resolve(directory, "../../../..");
435
544
  for (const artifact of inventory.research.permanentArtifacts) {
436
545
  const file = await boundResearchPath(projectRoot, artifact.path);
437
546
  if (await sha256File(file) !== artifact.sha256)
@@ -440,7 +549,7 @@ var init_io2 = __esm({
440
549
  };
441
550
  initializeResearchState = async (directory, spec, lineage) => {
442
551
  await import_promises2.default.mkdir(directory, { recursive: true });
443
- const file = import_node_path2.default.join(directory, "state.json");
552
+ const file = import_node_path3.default.join(directory, "state.json");
444
553
  let previous;
445
554
  try {
446
555
  previous = await readJson(file);
@@ -464,19 +573,19 @@ var init_io2 = __esm({
464
573
  status: "running",
465
574
  stages: {}
466
575
  };
467
- await writeJsonAtomic(import_node_path2.default.join(directory, "spec.json"), spec);
576
+ await writeJsonAtomic(import_node_path3.default.join(directory, "spec.json"), spec);
468
577
  await writeJsonAtomic(file, state);
469
578
  return state;
470
579
  };
471
- isRetainedDeletion = async (directory, file, hash) => {
580
+ isRetainedDeletion = async (directory, file, hash2) => {
472
581
  try {
473
- const inventory = await readJson(import_node_path2.default.join(directory, "retention.json"));
582
+ const inventory = await readJson(import_node_path3.default.join(directory, "retention.json"));
474
583
  const index = inventory.entries.findIndex(
475
- (entry) => entry.path === file && entry.sha256 === hash
584
+ (entry) => entry.path === file && entry.sha256 === hash2
476
585
  );
477
586
  if (index < 0) return false;
478
- const receipt = await readJson(import_node_path2.default.join(directory, "retention", `${index}.json`));
479
- return ["applying", "completed"].includes(receipt.status) && receipt.files[0]?.path === file && receipt.files[0]?.sha256 === hash;
587
+ const receipt = await readJson(import_node_path3.default.join(directory, "retention", `${index}.json`));
588
+ return ["applying", "completed"].includes(receipt.status) && receipt.files[0]?.path === file && receipt.files[0]?.sha256 === hash2;
480
589
  } catch (error) {
481
590
  if (error.code === "ENOENT") return false;
482
591
  throw error;
@@ -499,6 +608,7 @@ module.exports = __toCommonJS(strategyResearch_exports);
499
608
  init_io();
500
609
 
501
610
  // src/lib/aiPocketSearch/features.ts
611
+ var import_aiFeatures = require("@tradejs/core/aiFeatures");
502
612
  var DAY_MS = 24 * 60 * 60 * 1e3;
503
613
  var DAYS_PER_WEEK = 7;
504
614
  var DAYS_PER_MONTH = 30.4375;
@@ -867,15 +977,25 @@ var addFlattenedFeatures = ({
867
977
  value,
868
978
  segments,
869
979
  maxDepth,
870
- shouldSkipPath = () => false
980
+ shouldSkipPath = () => false,
981
+ onFeatureExcluded
871
982
  }) => {
872
983
  if (!segments.length && !isPlainRecord(value)) {
873
984
  return;
874
985
  }
875
- if (segments.length > maxDepth || isOutcomePath(segments) || shouldSkipPath(segments)) {
986
+ if (segments.length > maxDepth || isOutcomePath(segments)) {
987
+ onFeatureExcluded?.({
988
+ path: segments.join("."),
989
+ classification: segments.length > maxDepth ? "depth-limit" : "derived-policy"
990
+ });
876
991
  return;
877
992
  }
993
+ if (shouldSkipPath(segments)) return;
878
994
  if (Array.isArray(value)) {
995
+ onFeatureExcluded?.({
996
+ path: segments.join("."),
997
+ classification: "unprojected-array"
998
+ });
879
999
  return;
880
1000
  }
881
1001
  if (isFeaturePrimitive(value)) {
@@ -897,7 +1017,8 @@ var addFlattenedFeatures = ({
897
1017
  value: child,
898
1018
  segments: [...segments, key],
899
1019
  maxDepth,
900
- shouldSkipPath
1020
+ shouldSkipPath,
1021
+ onFeatureExcluded
901
1022
  });
902
1023
  }
903
1024
  };
@@ -1109,7 +1230,10 @@ var collectAiPocketFeatureSnapshot = ({
1109
1230
  value: source,
1110
1231
  segments: [],
1111
1232
  maxDepth: 8,
1233
+ onFeatureExcluded,
1112
1234
  shouldSkipPath: (segments) => {
1235
+ if (segments.join(".") === "additionalIndicators.baseContext.tickerProfile" || segments.join(".") === "additionalIndicators.tickerProfile" || segments.join(".") === "additionalIndicators.portfolioContext")
1236
+ return true;
1113
1237
  if (isOperatorExcluded(segments.join("."))) {
1114
1238
  return true;
1115
1239
  }
@@ -1127,6 +1251,10 @@ var collectAiPocketFeatureSnapshot = ({
1127
1251
  return true;
1128
1252
  }
1129
1253
  if (featureProfile === "compact" && isCompactFeaturePathSkipped(segments)) {
1254
+ onFeatureExcluded?.({
1255
+ path: segments.join("."),
1256
+ classification: "compact-profile"
1257
+ });
1130
1258
  return true;
1131
1259
  }
1132
1260
  if (featurePolicy !== "causal-stationary") {
@@ -1161,6 +1289,21 @@ var collectAiPocketFeatureSnapshot = ({
1161
1289
  delete features[key];
1162
1290
  }
1163
1291
  }
1292
+ const registered = payload.featureSnapshot;
1293
+ if (registered) {
1294
+ (0, import_aiFeatures.validateAiFeatureSnapshot)(registered);
1295
+ for (const definition of registered.definitions) {
1296
+ const path13 = `registered.${definition.id}`;
1297
+ if (isOperatorExcluded(path13)) continue;
1298
+ const value = registered.values[definition.id];
1299
+ 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";
1300
+ if (classification !== "eligible") {
1301
+ onFeatureExcluded?.({ path: path13, classification });
1302
+ continue;
1303
+ }
1304
+ features[path13] = value;
1305
+ }
1306
+ }
1164
1307
  return { features, featureCoverage };
1165
1308
  };
1166
1309
  var collectAiPocketFeatures = (options) => collectAiPocketFeatureSnapshot(options).features;
@@ -1779,15 +1922,15 @@ var createPocketResult = (rows, predicates, mask, validationRows, testRows, obje
1779
1922
  };
1780
1923
  };
1781
1924
  var hashMask = (mask) => {
1782
- let hash = 2166136261;
1925
+ let hash2 = 2166136261;
1783
1926
  for (let index = 0; index < mask.length; index += 1) {
1784
1927
  if (mask[index] !== 1) {
1785
1928
  continue;
1786
1929
  }
1787
- hash ^= index + 1;
1788
- hash = Math.imul(hash, 16777619);
1930
+ hash2 ^= index + 1;
1931
+ hash2 = Math.imul(hash2, 16777619);
1789
1932
  }
1790
- return (hash >>> 0).toString(36);
1933
+ return (hash2 >>> 0).toString(36);
1791
1934
  };
1792
1935
  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;
1793
1936
  var estimateCombinationCount = (poolSize, maxDepth, maxCombinations) => {
@@ -2484,7 +2627,52 @@ var policyVariant = (spec, coreId, policy, id, jev) => {
2484
2627
 
2485
2628
  // src/lib/strategyResearch/orchestrator.ts
2486
2629
  var import_promises5 = __toESM(require("fs/promises"));
2487
- var import_node_path6 = __toESM(require("path"));
2630
+
2631
+ // src/lib/researchMetricSnapshot.ts
2632
+ var import_node_crypto2 = require("crypto");
2633
+ var import_node_zlib = require("zlib");
2634
+ var MAX_BYTES = 256 * 1024 * 1024;
2635
+ var hash = (bytes) => (0, import_node_crypto2.createHash)("sha256").update(bytes).digest("hex");
2636
+ var validatePermanentResearchMetrics = (value) => {
2637
+ if (!value || typeof value !== "object") return;
2638
+ for (const [key, child] of Object.entries(value)) {
2639
+ if (["matchedPairs", "tradeRows", "allProjectedLeaves"].includes(key) && Array.isArray(child) && child.length)
2640
+ throw new Error(`Temporary payload ${key} is not a permanent metric`);
2641
+ if (key === "trades" && Array.isArray(child) && child.some(
2642
+ (row) => row && typeof row === "object" && ("signalId" in row || "entryTimestamp" in row)
2643
+ ))
2644
+ throw new Error("Temporary trade records are not permanent metrics");
2645
+ validatePermanentResearchMetrics(child);
2646
+ }
2647
+ };
2648
+ var encodeResearchMetricSnapshot = (metrics) => {
2649
+ validatePermanentResearchMetrics(metrics);
2650
+ const bytes = Buffer.from(JSON.stringify(metrics));
2651
+ if (bytes.length > MAX_BYTES)
2652
+ throw new Error("Metric snapshot exceeds decoded size limit");
2653
+ return {
2654
+ schema: "tradejs-metric-snapshot/v1",
2655
+ encoding: "gzip+base64",
2656
+ decodedBytes: bytes.length,
2657
+ sha256: hash(bytes),
2658
+ payload: (0, import_node_zlib.gzipSync)(bytes, { level: 9 }).toString("base64")
2659
+ };
2660
+ };
2661
+ var decodeResearchMetricSnapshot = (snapshot) => {
2662
+ 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")
2663
+ throw new Error("Invalid metric snapshot envelope");
2664
+ const bytes = (0, import_node_zlib.gunzipSync)(Buffer.from(snapshot.payload, "base64"), {
2665
+ maxOutputLength: snapshot.decodedBytes
2666
+ });
2667
+ if (bytes.length !== snapshot.decodedBytes || hash(bytes) !== snapshot.sha256)
2668
+ throw new Error("Metric snapshot checksum or size mismatch");
2669
+ const metrics = JSON.parse(bytes.toString("utf8"));
2670
+ validatePermanentResearchMetrics(metrics);
2671
+ return metrics;
2672
+ };
2673
+
2674
+ // src/lib/strategyResearch/orchestrator.ts
2675
+ var import_node_path7 = __toESM(require("path"));
2488
2676
 
2489
2677
  // src/lib/coreResearch/metrics.ts
2490
2678
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
@@ -3685,16 +3873,16 @@ var testCharts = (conclusion) => {
3685
3873
 
3686
3874
  // src/lib/strategyResearch/retention.ts
3687
3875
  var import_promises4 = __toESM(require("fs/promises"));
3688
- var import_node_path5 = __toESM(require("path"));
3876
+ var import_node_path6 = __toESM(require("path"));
3689
3877
 
3690
3878
  // src/lib/strategyRelease/researchRetention.ts
3691
3879
  var import_promises3 = __toESM(require("fs/promises"));
3692
- var import_node_path4 = __toESM(require("path"));
3693
- var import_node_crypto3 = require("crypto");
3880
+ var import_node_path5 = __toESM(require("path"));
3881
+ var import_node_crypto4 = require("crypto");
3694
3882
  var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
3695
3883
 
3696
3884
  // src/lib/strategyRelease/researchFileConsumers.ts
3697
- var import_node_path3 = __toESM(require("path"));
3885
+ var import_node_path4 = __toESM(require("path"));
3698
3886
  var import_node_child_process = require("child_process");
3699
3887
  var import_node_util = require("util");
3700
3888
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
@@ -3725,15 +3913,15 @@ print(json.dumps(sorted(opened)))
3725
3913
  `;
3726
3914
  var assertNoResearchFileConsumers = async (files, run = runCommand) => {
3727
3915
  const virtualPids = /* @__PURE__ */ new Set();
3728
- const wanted = new Set(files.map((file) => import_node_path3.default.resolve(file)));
3729
- let commonRoot = files.length ? import_node_path3.default.dirname(files[0]) : "";
3916
+ const wanted = new Set(files.map((file) => import_node_path4.default.resolve(file)));
3917
+ let commonRoot = files.length ? import_node_path4.default.dirname(files[0]) : "";
3730
3918
  while (commonRoot && files.some((file) => {
3731
- const relative = import_node_path3.default.relative(commonRoot, file);
3732
- return relative === ".." || relative.startsWith(`..${import_node_path3.default.sep}`) || import_node_path3.default.isAbsolute(relative);
3919
+ const relative = import_node_path4.default.relative(commonRoot, file);
3920
+ return relative === ".." || relative.startsWith(`..${import_node_path4.default.sep}`) || import_node_path4.default.isAbsolute(relative);
3733
3921
  }))
3734
- commonRoot = import_node_path3.default.dirname(commonRoot);
3922
+ commonRoot = import_node_path4.default.dirname(commonRoot);
3735
3923
  const selectors = [];
3736
- if (files.length > 1e3 && commonRoot !== import_node_path3.default.parse(commonRoot).root) {
3924
+ if (files.length > 1e3 && commonRoot !== import_node_path4.default.parse(commonRoot).root) {
3737
3925
  selectors.push(["+D", commonRoot]);
3738
3926
  } else {
3739
3927
  for (let offset = 0; offset < files.length; offset += 100)
@@ -3757,7 +3945,7 @@ var assertNoResearchFileConsumers = async (files, run = runCommand) => {
3757
3945
  if (line.startsWith("c"))
3758
3946
  virtual = line.startsWith("ccom.apple.Virtualization.");
3759
3947
  if (line.startsWith("n")) {
3760
- if (!wanted.has(import_node_path3.default.resolve(line.slice(1)))) continue;
3948
+ if (!wanted.has(import_node_path4.default.resolve(line.slice(1)))) continue;
3761
3949
  if (!virtual || !/^\d+$/.test(pid))
3762
3950
  throw new Error(
3763
3951
  `Research cleanup found open files: ${line.slice(1)}`
@@ -3791,9 +3979,9 @@ var assertNoResearchFileConsumers = async (files, run = runCommand) => {
3791
3979
  for (const mount of container.Mounts) {
3792
3980
  if (mount.Type !== "bind") continue;
3793
3981
  for (const file of files) {
3794
- const relative = import_node_path3.default.relative(mount.Source, file);
3795
- if (relative === "" || !relative.startsWith(`..${import_node_path3.default.sep}`) && relative !== ".." && !import_node_path3.default.isAbsolute(relative)) {
3796
- guestFiles.add(import_node_path3.default.posix.join(mount.Destination, relative));
3982
+ const relative = import_node_path4.default.relative(mount.Source, file);
3983
+ if (relative === "" || !relative.startsWith(`..${import_node_path4.default.sep}`) && relative !== ".." && !import_node_path4.default.isAbsolute(relative)) {
3984
+ guestFiles.add(import_node_path4.default.posix.join(mount.Destination, relative));
3797
3985
  covered.add(file);
3798
3986
  }
3799
3987
  }
@@ -3839,15 +4027,15 @@ var mapFiles = async (entries, visit) => {
3839
4027
  if (worker.status === "rejected") throw worker.reason;
3840
4028
  return results;
3841
4029
  };
3842
- 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);
4030
+ 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);
3843
4031
  var inside = (root, file) => {
3844
- const relative = import_node_path4.default.relative(root, file);
3845
- return relative !== "" && !relative.startsWith(`..${import_node_path4.default.sep}`) && relative !== ".." && !import_node_path4.default.isAbsolute(relative);
4032
+ const relative = import_node_path5.default.relative(root, file);
4033
+ return relative !== "" && !relative.startsWith(`..${import_node_path5.default.sep}`) && relative !== ".." && !import_node_path5.default.isAbsolute(relative);
3846
4034
  };
3847
4035
  var applyResearchRetention = async (options) => {
3848
4036
  const projectRoot = await import_promises3.default.realpath(options.projectRoot);
3849
- const dataRoot = import_node_path4.default.join(projectRoot, "data");
3850
- const resolve = (file) => import_node_path4.default.resolve(projectRoot, file);
4037
+ const dataRoot = import_node_path5.default.join(projectRoot, "data");
4038
+ const resolve = (file) => import_node_path5.default.resolve(projectRoot, file);
3851
4039
  const receiptPath = resolve(options.receiptPath);
3852
4040
  const journalPath = `${receiptPath}.deleted.jsonl`;
3853
4041
  const permanent = new Set(
@@ -3883,13 +4071,27 @@ var applyResearchRetention = async (options) => {
3883
4071
  const header = Buffer.alloc(4096);
3884
4072
  const { bytesRead } = await handle.read(header, 0, header.length, 0);
3885
4073
  const frontmatter = header.subarray(0, bytesRead).toString("utf8").match(/^---\r?\n([\s\S]*?)\r?\n---/);
3886
- if (!frontmatter || !/^schema:\s*tradejs-research\/v1\s*$/m.test(frontmatter[1]) || !(retired ? /^reproduction:\s*(?:complete|partial|blocked|legacy-partial)\s*$/m.test(
4074
+ if (!frontmatter || !/^schema:\s*tradejs-research\/v[12]\s*$/m.test(frontmatter[1]) || !(retired ? /^reproduction:\s*(?:complete|partial|blocked|legacy-partial)\s*$/m.test(
3887
4075
  frontmatter[1]
3888
4076
  ) : /^reproduction:\s*complete\s*$/m.test(frontmatter[1]))) {
3889
4077
  throw new Error(
3890
4078
  `Research note must have reproduction: complete: ${file}`
3891
4079
  );
3892
4080
  }
4081
+ if (!retired && /^schema:\s*tradejs-research\/v2\s*$/m.test(frontmatter[1])) {
4082
+ if ((await import_promises3.default.stat(file)).size > 8 * 1024 * 1024)
4083
+ throw new Error("Compact permanent note exceeds 8 MiB");
4084
+ const content = await import_promises3.default.readFile(file, "utf8");
4085
+ const section = content.split("## Metrics snapshot (machine-readable)")[1]?.split("## Reported metrics")[0] ?? "";
4086
+ const blocks = [
4087
+ ...section.matchAll(/```json\r?\n([\s\S]*?)\r?\n```/g)
4088
+ ];
4089
+ if (blocks.length !== 1)
4090
+ throw new Error(
4091
+ "Complete v2 note needs one verified metric snapshot"
4092
+ );
4093
+ decodeResearchMetricSnapshot(JSON.parse(blocks[0][1]));
4094
+ }
3893
4095
  } finally {
3894
4096
  await handle.close();
3895
4097
  }
@@ -3932,8 +4134,8 @@ var applyResearchRetention = async (options) => {
3932
4134
  }))
3933
4135
  };
3934
4136
  if (!options.apply) return receipt;
3935
- await import_promises3.default.mkdir(import_node_path4.default.dirname(receiptPath), { recursive: true });
3936
- if (await import_promises3.default.realpath(import_node_path4.default.dirname(receiptPath)) !== import_node_path4.default.dirname(receiptPath)) {
4137
+ await import_promises3.default.mkdir(import_node_path5.default.dirname(receiptPath), { recursive: true });
4138
+ if (await import_promises3.default.realpath(import_node_path5.default.dirname(receiptPath)) !== import_node_path5.default.dirname(receiptPath)) {
3937
4139
  throw new Error("Cleanup receipt refuses symlinked directories");
3938
4140
  }
3939
4141
  const lock = await import_promises3.default.open(`${receiptPath}.lock`, "wx");
@@ -3979,17 +4181,17 @@ var createResearchRetentionInventory = async (projectRoot, directory, spec, stat
3979
4181
  const permanentArtifacts = [];
3980
4182
  for (const [role, file] of [
3981
4183
  ["note", notePath],
3982
- ["metrics", import_node_path5.default.join(directory, "result.json")],
3983
- ["candidates", import_node_path5.default.join(directory, "03-freeze-gates/result.json")],
3984
- ["candidates", import_node_path5.default.join(directory, "resolved-spec.json")],
3985
- ["ledger", import_node_path5.default.join(directory, "05-freeze-primary/result.json")],
3986
- ["chart", import_node_path5.default.join(directory, "report.html")],
4184
+ ["metrics", import_node_path6.default.join(directory, "result.json")],
4185
+ ["candidates", import_node_path6.default.join(directory, "03-freeze-gates/result.json")],
4186
+ ["candidates", import_node_path6.default.join(directory, "resolved-spec.json")],
4187
+ ["ledger", import_node_path6.default.join(directory, "05-freeze-primary/result.json")],
4188
+ ["chart", import_node_path6.default.join(directory, "report.html")],
3987
4189
  ...[
3988
4190
  "source.patch",
3989
4191
  "source-baseline.tar",
3990
4192
  "source-untracked.json",
3991
4193
  "source-jev-models.json"
3992
- ].map((file2) => ["source", import_node_path5.default.join(directory, file2)])
4194
+ ].map((file2) => ["source", import_node_path6.default.join(directory, file2)])
3993
4195
  ]) {
3994
4196
  try {
3995
4197
  permanentArtifacts.push({
@@ -4003,11 +4205,11 @@ var createResearchRetentionInventory = async (projectRoot, directory, spec, stat
4003
4205
  }
4004
4206
  const files = /* @__PURE__ */ new Map();
4005
4207
  for (const stage of Object.values(state.stages)) {
4006
- for (const [relative, hash] of Object.entries(stage.artifacts)) {
4208
+ for (const [relative, hash2] of Object.entries(stage.artifacts)) {
4007
4209
  const file = await boundResearchPath(directory, relative);
4008
4210
  if (relative.endsWith("/result.json")) {
4009
4211
  const result = await readJson(file);
4010
- if (result?.dataset) files.set(file, hash);
4212
+ if (result?.dataset) files.set(file, hash2);
4011
4213
  }
4012
4214
  if (relative.endsWith("/sources.json") || relative.endsWith("/payloads.json")) {
4013
4215
  const sources = await readJson(file);
@@ -4024,13 +4226,13 @@ var createResearchRetentionInventory = async (projectRoot, directory, spec, stat
4024
4226
  }
4025
4227
  }
4026
4228
  const entries = [];
4027
- for (const [file, hash] of files) {
4229
+ for (const [file, hash2] of files) {
4028
4230
  const stat = await import_promises4.default.stat(file);
4029
4231
  entries.push({
4030
4232
  path: file,
4031
4233
  kind: "research_payload",
4032
4234
  researchId: spec.core.researchId,
4033
- sha256: hash,
4235
+ sha256: hash2,
4034
4236
  bytes: stat.size,
4035
4237
  createdAt: stat.birthtimeMs,
4036
4238
  verified: true,
@@ -4049,13 +4251,13 @@ var createResearchRetentionInventory = async (projectRoot, directory, spec, stat
4049
4251
  };
4050
4252
  var applyRetentionInventory = async (projectRoot, directory) => {
4051
4253
  const inventory = await readJson(
4052
- import_node_path5.default.join(directory, "retention.json")
4254
+ import_node_path6.default.join(directory, "retention.json")
4053
4255
  );
4054
- const receipts = import_node_path5.default.join(directory, "retention");
4256
+ const receipts = import_node_path6.default.join(directory, "retention");
4055
4257
  await import_promises4.default.mkdir(receipts, { recursive: true });
4056
4258
  for (let index = 0; index < inventory.entries.length; index++) {
4057
4259
  const entry = inventory.entries[index];
4058
- const receiptPath = import_node_path5.default.join(receipts, `${index}.json`);
4260
+ const receiptPath = import_node_path6.default.join(receipts, `${index}.json`);
4059
4261
  let receipt;
4060
4262
  try {
4061
4263
  receipt = await readJson(receiptPath);
@@ -4091,13 +4293,13 @@ var applyRetentionInventory = async (projectRoot, directory) => {
4091
4293
  entries: [entry],
4092
4294
  receiptPath,
4093
4295
  apply: true,
4094
- protectedPaths: [import_node_path5.default.join(directory, "retention.json")]
4296
+ protectedPaths: [import_node_path6.default.join(directory, "retention.json")]
4095
4297
  });
4096
4298
  }
4097
4299
  return { status: "completed", files: inventory.entries.length };
4098
4300
  };
4099
4301
  var finishResearchRetention = async (projectRoot, directory) => {
4100
- const statusPath = import_node_path5.default.join(directory, "retention-status.json");
4302
+ const statusPath = import_node_path6.default.join(directory, "retention-status.json");
4101
4303
  try {
4102
4304
  const result = await applyRetentionInventory(projectRoot, directory);
4103
4305
  await writeJsonAtomic(statusPath, result);
@@ -4124,7 +4326,7 @@ var runStrategyResearch = async (parameters) => {
4124
4326
  const directory = researchDirectory(projectRoot, spec);
4125
4327
  const lineage = await backend.lineage();
4126
4328
  const state = await initializeResearchState(directory, spec, lineage);
4127
- const statePath = import_node_path6.default.join(directory, "state.json");
4329
+ const statePath = import_node_path7.default.join(directory, "state.json");
4128
4330
  const elapsedBefore = state.elapsedMs ?? 0;
4129
4331
  const activeStart = Date.now();
4130
4332
  const elapsed = () => elapsedBefore + Date.now() - activeStart;
@@ -4134,7 +4336,7 @@ var runStrategyResearch = async (parameters) => {
4134
4336
  directory,
4135
4337
  state,
4136
4338
  conclusion: await readJson(
4137
- import_node_path6.default.join(directory, "result.json")
4339
+ import_node_path7.default.join(directory, "result.json")
4138
4340
  )
4139
4341
  };
4140
4342
  }
@@ -4144,13 +4346,13 @@ var runStrategyResearch = async (parameters) => {
4144
4346
  return writeJsonAtomic(statePath, state);
4145
4347
  };
4146
4348
  const event = async (stage2, status) => import_promises5.default.appendFile(
4147
- import_node_path6.default.join(directory, "events.jsonl"),
4349
+ import_node_path7.default.join(directory, "events.jsonl"),
4148
4350
  `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), stage: stage2, status, specSha256: state.specSha256 })}
4149
4351
  `
4150
4352
  );
4151
4353
  const stage = async (name, work) => {
4152
4354
  const stageDirectory = await boundResearchPath(directory, name);
4153
- const resultPath = import_node_path6.default.join(stageDirectory, "result.json");
4355
+ const resultPath = import_node_path7.default.join(stageDirectory, "result.json");
4154
4356
  if (state.stages[name]?.status === "completed")
4155
4357
  return readJson(resultPath);
4156
4358
  if (elapsed() > spec.budget.maxMinutes * 6e4)
@@ -4169,7 +4371,7 @@ var runStrategyResearch = async (parameters) => {
4169
4371
  await save();
4170
4372
  await event(name, "started");
4171
4373
  await import_promises5.default.writeFile(
4172
- import_node_path6.default.join(directory, "report.html"),
4374
+ import_node_path7.default.join(directory, "report.html"),
4173
4375
  buildStrategyResearchReport({ spec, state, diagnostics })
4174
4376
  );
4175
4377
  try {
@@ -4177,7 +4379,7 @@ var runStrategyResearch = async (parameters) => {
4177
4379
  await writeJsonAtomic(resultPath, result);
4178
4380
  const artifacts = {};
4179
4381
  for (const file of await import_promises5.default.readdir(stageDirectory)) {
4180
- const full = import_node_path6.default.join(stageDirectory, file);
4382
+ const full = import_node_path7.default.join(stageDirectory, file);
4181
4383
  if ((await import_promises5.default.stat(full)).isFile())
4182
4384
  artifacts[`${name}/${file}`] = await sha256File(full);
4183
4385
  }
@@ -4189,7 +4391,7 @@ var runStrategyResearch = async (parameters) => {
4189
4391
  "source-jev-models.json"
4190
4392
  ]) {
4191
4393
  try {
4192
- artifacts[file] = await sha256File(import_node_path6.default.join(directory, file));
4394
+ artifacts[file] = await sha256File(import_node_path7.default.join(directory, file));
4193
4395
  } catch (error) {
4194
4396
  if (error.code !== "ENOENT")
4195
4397
  throw error;
@@ -4222,14 +4424,14 @@ var runStrategyResearch = async (parameters) => {
4222
4424
  let previousRunId;
4223
4425
  try {
4224
4426
  previousRunId = (await readJson(
4225
- import_node_path6.default.join(folder, "native-run.json")
4427
+ import_node_path7.default.join(folder, "native-run.json")
4226
4428
  )).runId;
4227
4429
  } catch (error) {
4228
4430
  if (error.code !== "ENOENT") throw error;
4229
4431
  }
4230
4432
  if (!previousRunId) {
4231
4433
  try {
4232
- if ((await import_promises5.default.stat(import_node_path6.default.join(folder, "backtest.log"))).size)
4434
+ if ((await import_promises5.default.stat(import_node_path7.default.join(folder, "backtest.log"))).size)
4233
4435
  throw new Error(
4234
4436
  "Previous native attempt has no recorded run identity; inspect its log before another launch"
4235
4437
  );
@@ -4250,7 +4452,7 @@ var runStrategyResearch = async (parameters) => {
4250
4452
  },
4251
4453
  variant,
4252
4454
  folder,
4253
- async (runId) => writeJsonAtomic(import_node_path6.default.join(folder, "native-run.json"), { runId }),
4455
+ async (runId) => writeJsonAtomic(import_node_path7.default.join(folder, "native-run.json"), { runId }),
4254
4456
  previousRunId,
4255
4457
  confirmation
4256
4458
  );
@@ -4265,15 +4467,15 @@ var runStrategyResearch = async (parameters) => {
4265
4467
  try {
4266
4468
  const inventory = await stage("01-preflight", async () => {
4267
4469
  const prior = [];
4268
- const root = import_node_path6.default.dirname(directory);
4470
+ const root = import_node_path7.default.dirname(directory);
4269
4471
  for (const id of await import_promises5.default.readdir(root)) {
4270
4472
  if (id === spec.core.researchId || id.startsWith(".")) continue;
4271
4473
  try {
4272
4474
  const previous = await readJson(
4273
- import_node_path6.default.join(root, id, "spec.json")
4475
+ import_node_path7.default.join(root, id, "spec.json")
4274
4476
  );
4275
4477
  const previousState = await readJson(
4276
- import_node_path6.default.join(root, id, "state.json")
4478
+ import_node_path7.default.join(root, id, "state.json")
4277
4479
  );
4278
4480
  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);
4279
4481
  if (overlap && previousState.holdoutOpenedAt)
@@ -4299,7 +4501,7 @@ var runStrategyResearch = async (parameters) => {
4299
4501
  let previous;
4300
4502
  try {
4301
4503
  previous = (await readJson(
4302
- import_node_path6.default.join(folder, "native-run.json")
4504
+ import_node_path7.default.join(folder, "native-run.json")
4303
4505
  )).runId;
4304
4506
  } catch (error) {
4305
4507
  if (error.code !== "ENOENT")
@@ -4309,7 +4511,7 @@ var runStrategyResearch = async (parameters) => {
4309
4511
  spec,
4310
4512
  control2,
4311
4513
  folder,
4312
- async (runId) => writeJsonAtomic(import_node_path6.default.join(folder, "native-run.json"), {
4514
+ async (runId) => writeJsonAtomic(import_node_path7.default.join(folder, "native-run.json"), {
4313
4515
  runId
4314
4516
  }),
4315
4517
  previous
@@ -4323,7 +4525,7 @@ var runStrategyResearch = async (parameters) => {
4323
4525
  );
4324
4526
  spec = { ...spec, testSince: partition.testSince };
4325
4527
  }
4326
- await writeJsonAtomic(import_node_path6.default.join(directory, "resolved-spec.json"), spec);
4528
+ await writeJsonAtomic(import_node_path7.default.join(directory, "resolved-spec.json"), spec);
4327
4529
  let gates = [];
4328
4530
  let primaryId = null;
4329
4531
  let frozenSelection;
@@ -4382,10 +4584,10 @@ var runStrategyResearch = async (parameters) => {
4382
4584
  core: { ...spec.core, researchId: spec.frozenFrom }
4383
4585
  });
4384
4586
  const parentState = await readJson(
4385
- import_node_path6.default.join(parentDirectory, "state.json")
4587
+ import_node_path7.default.join(parentDirectory, "state.json")
4386
4588
  );
4387
4589
  const parentSpec = await readJson(
4388
- import_node_path6.default.join(parentDirectory, "spec.json")
4590
+ import_node_path7.default.join(parentDirectory, "spec.json")
4389
4591
  );
4390
4592
  const { verifyCompletedResearch: verifyCompletedResearch2 } = await Promise.resolve().then(() => (init_io2(), io_exports));
4391
4593
  await verifyCompletedResearch2(parentDirectory, parentState);
@@ -4412,10 +4614,10 @@ var runStrategyResearch = async (parameters) => {
4412
4614
  "Frozen revalidation requires complete, unchanged rules/runtime and a strictly new period"
4413
4615
  );
4414
4616
  gates = await readJson(
4415
- import_node_path6.default.join(parentDirectory, "03-freeze-gates/result.json")
4617
+ import_node_path7.default.join(parentDirectory, "03-freeze-gates/result.json")
4416
4618
  );
4417
4619
  frozenSelection = await readJson(
4418
- import_node_path6.default.join(parentDirectory, "05-freeze-primary/result.json")
4620
+ import_node_path7.default.join(parentDirectory, "05-freeze-primary/result.json")
4419
4621
  );
4420
4622
  if (frozenSelection.schema !== "tradejs-research-selection/v1" || !Array.isArray(frozenSelection.compositions))
4421
4623
  throw new Error(
@@ -4544,7 +4746,7 @@ var runStrategyResearch = async (parameters) => {
4544
4746
  `06-raw-metrics-${variant.id}`,
4545
4747
  async (folder) => {
4546
4748
  const resolved = spec.frozenFrom ? (await run(spec, variant, `06-raw-revalidation-${variant.id}`)).variant : (await readJson(
4547
- import_node_path6.default.join(directory, `02-raw-${variant.id}`, "result.json")
4749
+ import_node_path7.default.join(directory, `02-raw-${variant.id}`, "result.json")
4548
4750
  )).variant;
4549
4751
  const dataset = await backend.loadDataset(
4550
4752
  spec,
@@ -4721,7 +4923,7 @@ var runStrategyResearch = async (parameters) => {
4721
4923
  for (const name of Object.keys(state.stages)) {
4722
4924
  try {
4723
4925
  const result = await readJson(
4724
- import_node_path6.default.join(directory, name, "result.json")
4926
+ import_node_path7.default.join(directory, name, "result.json")
4725
4927
  );
4726
4928
  if (result?.variant)
4727
4929
  executions.push({ stage: name, ...result.variant });
@@ -4729,7 +4931,7 @@ var runStrategyResearch = async (parameters) => {
4729
4931
  if (error.code !== "ENOENT") throw error;
4730
4932
  }
4731
4933
  }
4732
- await writeJsonAtomic(import_node_path6.default.join(directory, "result.json"), {
4934
+ await writeJsonAtomic(import_node_path7.default.join(directory, "result.json"), {
4733
4935
  ...conclusion,
4734
4936
  executions,
4735
4937
  rawCore: Object.entries(rawReports).map(([id, windows]) => ({
@@ -4743,7 +4945,7 @@ var runStrategyResearch = async (parameters) => {
4743
4945
  delete state.error;
4744
4946
  await save();
4745
4947
  await import_promises5.default.writeFile(
4746
- import_node_path6.default.join(directory, "report.html"),
4948
+ import_node_path7.default.join(directory, "report.html"),
4747
4949
  buildStrategyResearchReport({
4748
4950
  spec,
4749
4951
  state,
@@ -4774,7 +4976,7 @@ var runStrategyResearch = async (parameters) => {
4774
4976
  )
4775
4977
  };
4776
4978
  const note = `---
4777
- schema: tradejs-research/v1
4979
+ schema: tradejs-research/v2
4778
4980
  strategy: '${spec.core.strategy}'
4779
4981
  date: '${spec.core.createdAt.slice(0, 10)}'
4780
4982
  kind: 'backtest'
@@ -4807,7 +5009,7 @@ ${JSON.stringify(spec, null, 2)}
4807
5009
  ## Metrics snapshot (machine-readable)
4808
5010
 
4809
5011
  \`\`\`json
4810
- ${JSON.stringify(compact, null, 2)}
5012
+ ${JSON.stringify(encodeResearchMetricSnapshot(compact))}
4811
5013
  \`\`\`
4812
5014
 
4813
5015
  ## Reported metrics
@@ -4826,8 +5028,12 @@ ${directory}/report.html
4826
5028
 
4827
5029
  ${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.
4828
5030
  `;
5031
+ if (Buffer.byteLength(note) > 8 * 1024 * 1024)
5032
+ throw new Error(
5033
+ "Compact permanent note exceeds 8 MiB; archive source separately, never truncate metrics"
5034
+ );
4829
5035
  await import_promises5.default.writeFile(
4830
- import_node_path6.default.join(
5036
+ import_node_path7.default.join(
4831
5037
  noteDirectory,
4832
5038
  `${spec.core.createdAt.slice(0, 10)}-${spec.core.researchId}.md`
4833
5039
  ),
@@ -4836,7 +5042,7 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
4836
5042
  ).catch(async (error) => {
4837
5043
  if (error.code !== "EEXIST") throw error;
4838
5044
  const existing = await import_promises5.default.readFile(
4839
- import_node_path6.default.join(
5045
+ import_node_path7.default.join(
4840
5046
  noteDirectory,
4841
5047
  `${spec.core.createdAt.slice(0, 10)}-${spec.core.researchId}.md`
4842
5048
  ),
@@ -4848,19 +5054,19 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
4848
5054
  );
4849
5055
  });
4850
5056
  await writeJsonAtomic(
4851
- import_node_path6.default.join(directory, "retention.json"),
5057
+ import_node_path7.default.join(directory, "retention.json"),
4852
5058
  await createResearchRetentionInventory(
4853
5059
  projectRoot,
4854
5060
  directory,
4855
5061
  spec,
4856
5062
  state,
4857
- import_node_path6.default.join(
5063
+ import_node_path7.default.join(
4858
5064
  noteDirectory,
4859
5065
  `${spec.core.createdAt.slice(0, 10)}-${spec.core.researchId}.md`
4860
5066
  )
4861
5067
  )
4862
5068
  );
4863
- await writeJsonAtomic(import_node_path6.default.join(directory, "manifest.json"), {
5069
+ await writeJsonAtomic(import_node_path7.default.join(directory, "manifest.json"), {
4864
5070
  schema: "tradejs-strategy-research-manifest/v1",
4865
5071
  specSha256: state.specSha256,
4866
5072
  artifacts: Object.fromEntries(
@@ -4874,7 +5080,7 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
4874
5080
  "retention.json"
4875
5081
  ].map(async (file) => [
4876
5082
  file,
4877
- await sha256File(import_node_path6.default.join(directory, file))
5083
+ await sha256File(import_node_path7.default.join(directory, file))
4878
5084
  ])
4879
5085
  )
4880
5086
  )
@@ -4887,7 +5093,7 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
4887
5093
  );
4888
5094
  }
4889
5095
  parameters.onProgress?.(
4890
- `\u0418\u0442\u043E\u0433: ${conclusion.status}. \u041E\u0442\u0447\u0451\u0442: ${import_node_path6.default.join(directory, "report.html")}`
5096
+ `\u0418\u0442\u043E\u0433: ${conclusion.status}. \u041E\u0442\u0447\u0451\u0442: ${import_node_path7.default.join(directory, "report.html")}`
4891
5097
  );
4892
5098
  return { directory, state, conclusion };
4893
5099
  } catch (error) {
@@ -4908,9 +5114,9 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
4908
5114
  frozenGates: 0
4909
5115
  }
4910
5116
  };
4911
- await writeJsonAtomic(import_node_path6.default.join(directory, "result.json"), conclusion);
5117
+ await writeJsonAtomic(import_node_path7.default.join(directory, "result.json"), conclusion);
4912
5118
  await import_promises5.default.writeFile(
4913
- import_node_path6.default.join(directory, "report.html"),
5119
+ import_node_path7.default.join(directory, "report.html"),
4914
5120
  buildStrategyResearchReport({ spec, state, conclusion, diagnostics })
4915
5121
  );
4916
5122
  throw error;
@@ -4933,10 +5139,10 @@ var import_registry = require("@tradejs/node/registry");
4933
5139
  var import_connectors = require("@tradejs/node/connectors");
4934
5140
 
4935
5141
  // src/lib/coreResearch/dataset.ts
4936
- var import_node_crypto4 = require("crypto");
4937
- var import_node_fs2 = require("fs");
4938
- var import_node_path7 = __toESM(require("path"));
4939
- var import_node_readline = __toESM(require("readline"));
5142
+ var import_node_crypto5 = require("crypto");
5143
+ var import_node_fs3 = require("fs");
5144
+ var import_node_path8 = __toESM(require("path"));
5145
+ var import_node_readline2 = __toESM(require("readline"));
4940
5146
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4941
5147
  var finiteNumber = (value, field) => {
4942
5148
  const numeric = Number(value);
@@ -5146,11 +5352,11 @@ var readCoreResearchVariant = async (variant) => {
5146
5352
  const seen = /* @__PURE__ */ new Map();
5147
5353
  let duplicateRowsDropped = 0;
5148
5354
  for (const inputPath of variant.files) {
5149
- const filePath = import_node_path7.default.resolve(inputPath);
5150
- const input = (0, import_node_fs2.createReadStream)(filePath);
5151
- const hash = (0, import_node_crypto4.createHash)("sha256");
5152
- input.on("data", (chunk) => hash.update(chunk));
5153
- const lines = import_node_readline.default.createInterface({ input, crlfDelay: Infinity });
5355
+ const filePath = import_node_path8.default.resolve(inputPath);
5356
+ const input = (0, import_node_fs3.createReadStream)(filePath);
5357
+ const hash2 = (0, import_node_crypto5.createHash)("sha256");
5358
+ input.on("data", (chunk) => hash2.update(chunk));
5359
+ const lines = import_node_readline2.default.createInterface({ input, crlfDelay: Infinity });
5154
5360
  const fileTrades = [];
5155
5361
  let lineNumber = 0;
5156
5362
  let rows = 0;
@@ -5199,7 +5405,7 @@ var readCoreResearchVariant = async (variant) => {
5199
5405
  trades.push(trade);
5200
5406
  fileTrades.push(trade);
5201
5407
  }
5202
- const fileSha2562 = hash.digest("hex");
5408
+ const fileSha2562 = hash2.digest("hex");
5203
5409
  for (const trade of fileTrades) trade.sourceSha256 = fileSha2562;
5204
5410
  sources.push({
5205
5411
  path: filePath,
@@ -5219,7 +5425,7 @@ var readCoreResearchVariant = async (variant) => {
5219
5425
  };
5220
5426
 
5221
5427
  // src/lib/backtest/checkpoint.ts
5222
- var import_node_crypto5 = require("crypto");
5428
+ var import_node_crypto6 = require("crypto");
5223
5429
  var import_constants = require("@tradejs/core/constants");
5224
5430
  var import_backtest2 = require("@tradejs/core/backtest");
5225
5431
  var import_redis = require("@tradejs/infra/redis");
@@ -5419,6 +5625,7 @@ var extractSignalFromAiDatasetRow = (row) => {
5419
5625
  const { payload } = row;
5420
5626
  return {
5421
5627
  ...payload.signal,
5628
+ aiFeatureReplay: { snapshot: payload.featureSnapshot ?? null },
5422
5629
  strategy: payload.signal.strategy,
5423
5630
  figures: payload.figures ?? {},
5424
5631
  indicators: payload.indicators ?? {},
@@ -5432,9 +5639,9 @@ init_io2();
5432
5639
 
5433
5640
  // src/lib/aiTrainResearch.ts
5434
5641
  var import_node_child_process2 = require("child_process");
5435
- var import_node_crypto7 = require("crypto");
5642
+ var import_node_crypto8 = require("crypto");
5436
5643
  var import_promises7 = __toESM(require("fs/promises"));
5437
- var import_node_path9 = __toESM(require("path"));
5644
+ var import_node_path10 = __toESM(require("path"));
5438
5645
 
5439
5646
  // src/lib/aiTrainMetrics.ts
5440
5647
  var DAY_MS3 = 24 * 60 * 60 * 1e3;
@@ -5444,10 +5651,10 @@ var import_strategies = require("@tradejs/node/strategies");
5444
5651
  var import_types2 = require("@tradejs/types");
5445
5652
 
5446
5653
  // src/lib/strategyGateFingerprint.ts
5447
- var import_node_crypto6 = require("crypto");
5654
+ var import_node_crypto7 = require("crypto");
5448
5655
  var import_node_module = require("module");
5449
5656
  var import_promises6 = __toESM(require("fs/promises"));
5450
- var import_node_path8 = __toESM(require("path"));
5657
+ var import_node_path9 = __toESM(require("path"));
5451
5658
 
5452
5659
  // src/lib/aiTrainResearch.ts
5453
5660
  var DAY_MS4 = 24 * 60 * 60 * 1e3;
@@ -5457,6 +5664,8 @@ var RESEARCH_CONTEXT_ENV_KEYS = [
5457
5664
  "INTERVAL",
5458
5665
  "DERIVATIVES_CONTEXT_ENABLED",
5459
5666
  "DERIVATIVES_CONTEXT_TARGET_ENABLED",
5667
+ "TICKER_PROFILES_PROVIDER",
5668
+ "TICKER_PROFILES_UNIVERSE",
5460
5669
  "DERIVATIVES_CONTEXT_LOOKBACK_HOURS",
5461
5670
  "DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS",
5462
5671
  "DERIVATIVES_CONTEXT_EXCHANGE_PRIORITY",
@@ -5486,7 +5695,7 @@ var normalizeForStableJson = (value) => {
5486
5695
  }
5487
5696
  return value;
5488
5697
  };
5489
- var fingerprintResearchValue = (value) => (0, import_node_crypto7.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
5698
+ var fingerprintResearchValue = (value) => (0, import_node_crypto8.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
5490
5699
  var fingerprintResearchContextEnvironment = (env = process.env) => fingerprintResearchValue(
5491
5700
  Object.fromEntries(
5492
5701
  RESEARCH_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
@@ -5496,81 +5705,7 @@ var fingerprintResearchContextEnvironment = (env = process.env) => fingerprintRe
5496
5705
  // src/lib/strategyResearch/native.ts
5497
5706
  var import_data = require("@tradejs/core/data");
5498
5707
  var import_coreResearch = require("@tradejs/infra/coreResearch");
5499
-
5500
- // src/lib/coreResearch/trace.ts
5501
- var import_node_fs3 = require("fs");
5502
- var import_node_readline2 = __toESM(require("readline"));
5503
- var import_node_path10 = __toESM(require("path"));
5504
- var summarizeCoreResearchTrace = async (filePaths = []) => {
5505
- const events = /* @__PURE__ */ Object.create(null);
5506
- const skipCounts = /* @__PURE__ */ Object.create(null);
5507
- const exitCodes = /* @__PURE__ */ Object.create(null);
5508
- const policyRejections = {};
5509
- const policyExamples = [];
5510
- for (const inputPath of filePaths) {
5511
- const filePath = import_node_path10.default.resolve(inputPath);
5512
- const input = (0, import_node_fs3.createReadStream)(filePath);
5513
- const lines = import_node_readline2.default.createInterface({ input, crlfDelay: Infinity });
5514
- let index = -1;
5515
- try {
5516
- for await (const line of lines) {
5517
- index++;
5518
- if (!line.trim()) continue;
5519
- let event;
5520
- try {
5521
- event = JSON.parse(line);
5522
- } catch (error) {
5523
- throw new Error(
5524
- `${filePath}:${index + 1} contains invalid trace JSON: ${String(error)}`
5525
- );
5526
- }
5527
- events[event.event] = (events[event.event] ?? 0) + 1;
5528
- if (event.event === "skip_summary") {
5529
- for (const [reason, count] of Object.entries(event.skipCounts)) {
5530
- skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
5531
- }
5532
- }
5533
- if (event.event === "position_exited" && event.exitCode) {
5534
- exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
5535
- }
5536
- if ("researchEntryDecision" in event && event.researchEntryDecision && !event.researchEntryDecision.allowed) {
5537
- const reason = event.researchEntryDecision.reason;
5538
- policyRejections[reason] = (policyRejections[reason] ?? 0) + 1;
5539
- if (policyExamples.length < 20)
5540
- policyExamples.push({
5541
- timestamp: event.timestamp,
5542
- symbol: event.symbol,
5543
- decision: event.researchEntryDecision
5544
- });
5545
- }
5546
- }
5547
- } finally {
5548
- lines.close();
5549
- input.destroy();
5550
- }
5551
- }
5552
- return {
5553
- events: Object.fromEntries(Object.entries(events).sort()),
5554
- skipCounts: Object.fromEntries(
5555
- Object.entries(skipCounts).sort(
5556
- ([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
5557
- )
5558
- ),
5559
- exitCodes: Object.fromEntries(
5560
- Object.entries(exitCodes).sort(
5561
- ([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
5562
- )
5563
- ),
5564
- ...policyExamples.length ? {
5565
- researchPolicy: {
5566
- rejections: policyRejections,
5567
- examples: policyExamples
5568
- }
5569
- } : {}
5570
- };
5571
- };
5572
-
5573
- // src/lib/strategyResearch/native.ts
5708
+ init_trace();
5574
5709
  var import_jev4 = require("@tradejs/core/jev");
5575
5710
 
5576
5711
  // src/lib/backtest/jevOptions.ts