@tradejs/cli 2.0.19 → 2.0.21

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.
@@ -169,7 +169,8 @@ var extractRuntimeTradeRows = (runtimePayload) => {
169
169
  const signal = asRecord(redisValues?.signal) ?? (signalId ? signalsById.get(signalId) ?? null : null) ?? (shapeKey ? signalsByShape.get(shapeKey) ?? null : null);
170
170
  return {
171
171
  trade,
172
- signal
172
+ signal,
173
+ runtimeLineage: asRecord(trade.runtimeLineage) ?? null
173
174
  };
174
175
  }).filter((row) => row != null);
175
176
  };
@@ -471,7 +472,8 @@ var buildSample = ({
471
472
  direction: direction ?? match?.direction ?? null,
472
473
  expectedPrice: match?.backtestPrice ?? null,
473
474
  actualPrice: match?.runtimePrice ?? null
474
- })
475
+ }),
476
+ runtimeLineage: row.runtimeLineage
475
477
  };
476
478
  };
477
479
  var buildReplayOnlySample = (match) => ({
@@ -518,7 +520,8 @@ var buildReplayOnlySample = (match) => ({
518
520
  direction: match.direction,
519
521
  expectedPrice: match.backtestPrice,
520
522
  actualPrice: match.runtimePrice
521
- })
523
+ }),
524
+ runtimeLineage: null
522
525
  });
523
526
  var hasTelemetry = (sample) => sample.signalClosePrice != null || sample.arrivalMid != null || sample.orderSubmitTime != null || sample.fillAvgPrice != null || sample.fillTime != null || sample.fee != null;
524
527
  var hasFullTelemetry = (sample) => sample.signalClosePrice != null && sample.arrivalMid != null && sample.orderSubmitTime != null && sample.orderAckTime != null && sample.fillAvgPrice != null && sample.fillTime != null;
@@ -2700,27 +2700,27 @@ var createCoinMarketCapFetchStats = () => ({
2700
2700
  totalCredits: 0,
2701
2701
  byPath: /* @__PURE__ */ new Map()
2702
2702
  });
2703
- var getCoinMarketCapPathLabel = (path4) => {
2704
- if (path4.includes("global-metrics")) return "global";
2705
- if (path4.includes("cryptocurrency/quotes")) return "reference";
2706
- if (path4.includes("exchange/quotes")) return "exchangeLiquidity";
2707
- if (path4.includes("fear-and-greed")) return "fearGreed";
2708
- if (path4.includes("cmc100")) return "cmc100";
2709
- if (path4.includes("cmc20")) return "cmc20";
2710
- return path4;
2711
- };
2712
- var recordCoinMarketCapFetchStats = (stats, path4, creditCount) => {
2703
+ var getCoinMarketCapPathLabel = (path5) => {
2704
+ if (path5.includes("global-metrics")) return "global";
2705
+ if (path5.includes("cryptocurrency/quotes")) return "reference";
2706
+ if (path5.includes("exchange/quotes")) return "exchangeLiquidity";
2707
+ if (path5.includes("fear-and-greed")) return "fearGreed";
2708
+ if (path5.includes("cmc100")) return "cmc100";
2709
+ if (path5.includes("cmc20")) return "cmc20";
2710
+ return path5;
2711
+ };
2712
+ var recordCoinMarketCapFetchStats = (stats, path5, creditCount) => {
2713
2713
  if (!stats) return;
2714
- const existing = stats.byPath.get(path4) ?? { requests: 0, credits: 0 };
2714
+ const existing = stats.byPath.get(path5) ?? { requests: 0, credits: 0 };
2715
2715
  existing.requests += 1;
2716
2716
  existing.credits += creditCount ?? 0;
2717
2717
  stats.requests += 1;
2718
2718
  stats.totalCredits += creditCount ?? 0;
2719
- stats.byPath.set(path4, existing);
2719
+ stats.byPath.set(path5, existing);
2720
2720
  };
2721
2721
  var formatCoinMarketCapFetchStats = (stats) => {
2722
2722
  const byPath = [...stats.byPath.entries()].map(
2723
- ([path4, item]) => `${getCoinMarketCapPathLabel(path4)}=${item.requests}req/${item.credits}cr`
2723
+ ([path5, item]) => `${getCoinMarketCapPathLabel(path5)}=${item.requests}req/${item.credits}cr`
2724
2724
  ).join(" ");
2725
2725
  return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
2726
2726
  };
@@ -4901,11 +4901,57 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
4901
4901
 
4902
4902
  // src/lib/runtimeLineage.ts
4903
4903
  var import_node_child_process = require("child_process");
4904
+ var import_node_crypto2 = require("crypto");
4905
+ var import_promises2 = __toESM(require("fs/promises"));
4906
+ var import_node_path2 = __toESM(require("path"));
4907
+ var import_types2 = require("@tradejs/types");
4908
+ var import_strategies3 = require("@tradejs/node/strategies");
4909
+ var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
4910
+
4911
+ // src/lib/strategyGateFingerprint.ts
4904
4912
  var import_node_crypto = require("crypto");
4905
4913
  var import_promises = __toESM(require("fs/promises"));
4906
4914
  var import_node_path = __toESM(require("path"));
4907
- var import_types2 = require("@tradejs/types");
4908
- var import_strategies3 = require("@tradejs/node/strategies");
4915
+ var gateFingerprintRelativePaths = (strategyName) => [
4916
+ `packages/strategies/src/${strategyName}/adapters/ai.ts`,
4917
+ `packages/strategies/src/${strategyName}/guardrails.ts`,
4918
+ `packages/strategies/src/${strategyName}/pockets.ts`,
4919
+ `packages/strategies/src/${strategyName}/config.ts`,
4920
+ "packages/strategies/src/shared/localAiGate.ts",
4921
+ "packages/node/src/ai.ts",
4922
+ "packages/core/src/utils/strategyHelpers/signalBuilders.ts"
4923
+ ];
4924
+ var resolveStrategyGateFingerprint = async ({
4925
+ projectRoot,
4926
+ strategyName,
4927
+ gitSha
4928
+ }) => {
4929
+ const candidates = await Promise.all(
4930
+ gateFingerprintRelativePaths(strategyName).map(async (relativePath) => {
4931
+ const content = await import_promises.default.readFile(import_node_path.default.join(projectRoot, relativePath)).catch(() => null);
4932
+ return content == null ? null : { relativePath, content };
4933
+ })
4934
+ );
4935
+ const sourceEntries = candidates.filter(
4936
+ (entry) => entry != null
4937
+ );
4938
+ const hash = (0, import_node_crypto.createHash)("sha256");
4939
+ hash.update(strategyName);
4940
+ if (sourceEntries.length) {
4941
+ for (const entry of sourceEntries) {
4942
+ hash.update(entry.relativePath);
4943
+ hash.update(entry.content);
4944
+ }
4945
+ } else {
4946
+ hash.update(gitSha ?? "unknown-git-sha");
4947
+ }
4948
+ return {
4949
+ gateFingerprint: hash.digest("hex").slice(0, 16),
4950
+ gateFingerprintFiles: sourceEntries.map((entry) => entry.relativePath)
4951
+ };
4952
+ };
4953
+
4954
+ // src/lib/runtimeLineage.ts
4909
4955
  var RUNTIME_CONTEXT_ENV_KEYS = [
4910
4956
  "AI_MODE",
4911
4957
  "MIN_AI_QUALITY",
@@ -4968,7 +5014,7 @@ var resolveRuntimeMaxLossValue = (config) => {
4968
5014
  );
4969
5015
  return Number.isFinite(maxLossValue) ? maxLossValue : null;
4970
5016
  };
4971
- var fingerprintRuntimeValue = (value) => (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
5017
+ var fingerprintRuntimeValue = (value) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
4972
5018
  var gitLineageCache = /* @__PURE__ */ new Map();
4973
5019
  var gateFingerprintCache = /* @__PURE__ */ new Map();
4974
5020
  var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
@@ -5019,7 +5065,7 @@ var resolveGitLineage = (projectRoot) => {
5019
5065
  };
5020
5066
  var readOptionalFile = async (filePath) => {
5021
5067
  try {
5022
- return await import_promises.default.readFile(filePath);
5068
+ return await import_promises2.default.readFile(filePath);
5023
5069
  } catch {
5024
5070
  return null;
5025
5071
  }
@@ -5029,7 +5075,7 @@ var resolveBinanceBreadthFingerprint = async (projectRoot) => {
5029
5075
  return binanceBreadthFingerprintCache.get(projectRoot) ?? null;
5030
5076
  }
5031
5077
  const content = await readOptionalFile(
5032
- import_node_path.default.join(
5078
+ import_node_path2.default.join(
5033
5079
  projectRoot,
5034
5080
  "packages/node/src/config/binanceBreadthUniverses.json"
5035
5081
  )
@@ -5051,7 +5097,7 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
5051
5097
  if (snapshotFingerprintCache.has(cacheKey)) {
5052
5098
  return snapshotFingerprintCache.get(cacheKey) ?? null;
5053
5099
  }
5054
- const content = await readOptionalFile(import_node_path.default.join(projectRoot, relativePath));
5100
+ const content = await readOptionalFile(import_node_path2.default.join(projectRoot, relativePath));
5055
5101
  let result = null;
5056
5102
  if (content) {
5057
5103
  try {
@@ -5064,55 +5110,26 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
5064
5110
  snapshotFingerprintCache.set(cacheKey, result);
5065
5111
  return result;
5066
5112
  };
5067
- var resolveGateFingerprint = async ({
5113
+ var resolveRuntimeGateFingerprint = async ({
5068
5114
  projectRoot,
5069
5115
  strategyName,
5070
5116
  gitSha
5071
5117
  }) => {
5072
5118
  const cacheKey = `${projectRoot}:${strategyName}:${gitSha ?? "unknown"}`;
5073
5119
  const cached = gateFingerprintCache.get(cacheKey);
5074
- if (cached) {
5075
- return cached;
5076
- }
5077
- const relativeCandidates = [
5078
- `packages/strategies/src/${strategyName}/adapters/ai.ts`,
5079
- `packages/strategies/src/${strategyName}/guardrails.ts`,
5080
- `packages/strategies/src/${strategyName}/pockets.ts`,
5081
- `packages/strategies/src/${strategyName}/config.ts`,
5082
- "packages/node/src/ai.ts",
5083
- "packages/core/src/utils/strategyHelpers/signalBuilders.ts"
5084
- ];
5085
- const optionalEntries = await Promise.all(
5086
- relativeCandidates.map(async (relativePath) => {
5087
- const content = await readOptionalFile(
5088
- import_node_path.default.join(projectRoot, relativePath)
5089
- );
5090
- return content == null ? null : { relativePath, content };
5091
- })
5092
- );
5093
- const sourceEntries = [];
5094
- for (const entry of optionalEntries) {
5095
- if (entry != null) {
5096
- sourceEntries.push(entry);
5097
- }
5098
- }
5099
- const hash = (0, import_node_crypto.createHash)("sha256");
5100
- hash.update(strategyName);
5101
- if (sourceEntries.length) {
5102
- for (const entry of sourceEntries) {
5103
- hash.update(entry.relativePath);
5104
- hash.update(entry.content);
5105
- }
5106
- } else {
5107
- hash.update(gitSha ?? "unknown-git-sha");
5108
- }
5109
- const fingerprint = hash.digest("hex").slice(0, 16);
5110
- gateFingerprintCache.set(cacheKey, fingerprint);
5111
- return fingerprint;
5120
+ if (cached) return cached;
5121
+ const result = await resolveStrategyGateFingerprint({
5122
+ projectRoot,
5123
+ strategyName,
5124
+ gitSha
5125
+ });
5126
+ gateFingerprintCache.set(cacheKey, result);
5127
+ return result;
5112
5128
  };
5113
5129
  var buildRuntimeLineage = async ({
5114
5130
  projectRoot,
5115
5131
  strategyName,
5132
+ compositionId,
5116
5133
  config,
5117
5134
  runContext,
5118
5135
  env = process.env
@@ -5138,19 +5155,23 @@ var buildRuntimeLineage = async ({
5138
5155
  };
5139
5156
  return {
5140
5157
  schemaVersion: 1,
5158
+ compositionId: compositionId?.trim() || null,
5141
5159
  ...git,
5142
- gateFingerprint: await resolveGateFingerprint({
5160
+ gateFingerprint: (await resolveRuntimeGateFingerprint({
5143
5161
  projectRoot,
5144
5162
  strategyName,
5145
5163
  gitSha: git.gitSha
5146
- }),
5147
- configFingerprint: fingerprintRuntimeValue(config),
5164
+ })).gateFingerprint,
5165
+ // Position risk is tracked separately so changing trade size does not
5166
+ // masquerade as a change to the core + gate decision logic.
5167
+ configFingerprint: (0, import_strategyReleaseEvidence.strategyLogicConfigFingerprint)(config),
5148
5168
  contextFingerprint: fingerprintRuntimeValue(context),
5149
5169
  maxLossValue: resolveRuntimeMaxLossValue(config)
5150
5170
  };
5151
5171
  };
5152
5172
  var runtimeLineageKey = (lineage) => [
5153
5173
  lineage.schemaVersion,
5174
+ lineage.compositionId ?? "unbound-composition",
5154
5175
  lineage.gitSha ?? "unknown",
5155
5176
  lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
5156
5177
  lineage.gateFingerprint,
@@ -7163,8 +7184,8 @@ var buildReplayExchangeComparisonDetails = ({
7163
7184
  };
7164
7185
 
7165
7186
  // src/lib/replay/runtimeEvidenceSource.ts
7166
- var import_promises2 = __toESM(require("fs/promises"));
7167
- var import_node_path2 = __toESM(require("path"));
7187
+ var import_promises3 = __toESM(require("fs/promises"));
7188
+ var import_node_path3 = __toESM(require("path"));
7168
7189
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
7169
7190
  var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
7170
7191
  var unwrapRows = (value, field) => (Array.isArray(value) ? value : []).map((item) => {
@@ -7225,9 +7246,9 @@ var loadReplayRuntimeEvidenceSource = async ({
7225
7246
  expectedUserName,
7226
7247
  expectedWindow
7227
7248
  }) => {
7228
- const resolvedPath = import_node_path2.default.resolve(projectRoot, filePath);
7249
+ const resolvedPath = import_node_path3.default.resolve(projectRoot, filePath);
7229
7250
  const root = asRecord(
7230
- JSON.parse(await import_promises2.default.readFile(resolvedPath, "utf8"))
7251
+ JSON.parse(await import_promises3.default.readFile(resolvedPath, "utf8"))
7231
7252
  );
7232
7253
  const runtime = asRecord(root?.runtime) ?? root;
7233
7254
  if (!root || !runtime) {
@@ -8274,8 +8295,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
8274
8295
  };
8275
8296
 
8276
8297
  // src/lib/replay/outputReport.ts
8277
- var import_promises3 = __toESM(require("fs/promises"));
8278
- var import_node_path3 = __toESM(require("path"));
8298
+ var import_promises4 = __toESM(require("fs/promises"));
8299
+ var import_node_path4 = __toESM(require("path"));
8279
8300
  var import_data = require("@tradejs/core/data");
8280
8301
  var formatMsk = (timestamp) => typeof timestamp === "number" && Number.isFinite(timestamp) ? new Intl.DateTimeFormat("ru-RU", {
8281
8302
  timeZone: "Europe/Moscow",
@@ -8579,8 +8600,8 @@ var writeReplayOutputReport = async ({
8579
8600
  strategySnapshot,
8580
8601
  runtimeComparison
8581
8602
  }) => {
8582
- const outputDir = import_node_path3.default.resolve(projectRoot, "data/replay/output");
8583
- await import_promises3.default.mkdir(outputDir, { recursive: true });
8603
+ const outputDir = import_node_path4.default.resolve(projectRoot, "data/replay/output");
8604
+ await import_promises4.default.mkdir(outputDir, { recursive: true });
8584
8605
  const perTradeAnalysis = buildPerTradeAnalysis(runtimeComparison);
8585
8606
  const report = {
8586
8607
  reportType: "replay-output",
@@ -8615,8 +8636,8 @@ var writeReplayOutputReport = async ({
8615
8636
  runtimeComparison,
8616
8637
  perTradeAnalysis
8617
8638
  };
8618
- const markdownPath = import_node_path3.default.join(outputDir, `${timestamp}-replay-report.md`);
8619
- const jsonPath = import_node_path3.default.join(outputDir, `${timestamp}-replay-report.json`);
8639
+ const markdownPath = import_node_path4.default.join(outputDir, `${timestamp}-replay-report.md`);
8640
+ const jsonPath = import_node_path4.default.join(outputDir, `${timestamp}-replay-report.json`);
8620
8641
  const lines = [
8621
8642
  "# Replay Report",
8622
8643
  "",
@@ -8682,8 +8703,8 @@ var writeReplayOutputReport = async ({
8682
8703
  ""
8683
8704
  ];
8684
8705
  await Promise.all([
8685
- import_promises3.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
8686
- import_promises3.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
8706
+ import_promises4.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
8707
+ import_promises4.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
8687
8708
  `, "utf8")
8688
8709
  ]);
8689
8710
  return {
@@ -2703,27 +2703,27 @@ var createCoinMarketCapFetchStats = () => ({
2703
2703
  totalCredits: 0,
2704
2704
  byPath: /* @__PURE__ */ new Map()
2705
2705
  });
2706
- var getCoinMarketCapPathLabel = (path4) => {
2707
- if (path4.includes("global-metrics")) return "global";
2708
- if (path4.includes("cryptocurrency/quotes")) return "reference";
2709
- if (path4.includes("exchange/quotes")) return "exchangeLiquidity";
2710
- if (path4.includes("fear-and-greed")) return "fearGreed";
2711
- if (path4.includes("cmc100")) return "cmc100";
2712
- if (path4.includes("cmc20")) return "cmc20";
2713
- return path4;
2714
- };
2715
- var recordCoinMarketCapFetchStats = (stats, path4, creditCount) => {
2706
+ var getCoinMarketCapPathLabel = (path5) => {
2707
+ if (path5.includes("global-metrics")) return "global";
2708
+ if (path5.includes("cryptocurrency/quotes")) return "reference";
2709
+ if (path5.includes("exchange/quotes")) return "exchangeLiquidity";
2710
+ if (path5.includes("fear-and-greed")) return "fearGreed";
2711
+ if (path5.includes("cmc100")) return "cmc100";
2712
+ if (path5.includes("cmc20")) return "cmc20";
2713
+ return path5;
2714
+ };
2715
+ var recordCoinMarketCapFetchStats = (stats, path5, creditCount) => {
2716
2716
  if (!stats) return;
2717
- const existing = stats.byPath.get(path4) ?? { requests: 0, credits: 0 };
2717
+ const existing = stats.byPath.get(path5) ?? { requests: 0, credits: 0 };
2718
2718
  existing.requests += 1;
2719
2719
  existing.credits += creditCount ?? 0;
2720
2720
  stats.requests += 1;
2721
2721
  stats.totalCredits += creditCount ?? 0;
2722
- stats.byPath.set(path4, existing);
2722
+ stats.byPath.set(path5, existing);
2723
2723
  };
2724
2724
  var formatCoinMarketCapFetchStats = (stats) => {
2725
2725
  const byPath = [...stats.byPath.entries()].map(
2726
- ([path4, item]) => `${getCoinMarketCapPathLabel(path4)}=${item.requests}req/${item.credits}cr`
2726
+ ([path5, item]) => `${getCoinMarketCapPathLabel(path5)}=${item.requests}req/${item.credits}cr`
2727
2727
  ).join(" ");
2728
2728
  return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
2729
2729
  };
@@ -4904,11 +4904,57 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
4904
4904
 
4905
4905
  // src/lib/runtimeLineage.ts
4906
4906
  var import_node_child_process = require("child_process");
4907
+ var import_node_crypto2 = require("crypto");
4908
+ var import_promises2 = __toESM(require("fs/promises"));
4909
+ var import_node_path2 = __toESM(require("path"));
4910
+ var import_types2 = require("@tradejs/types");
4911
+ var import_strategies3 = require("@tradejs/node/strategies");
4912
+ var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
4913
+
4914
+ // src/lib/strategyGateFingerprint.ts
4907
4915
  var import_node_crypto = require("crypto");
4908
4916
  var import_promises = __toESM(require("fs/promises"));
4909
4917
  var import_node_path = __toESM(require("path"));
4910
- var import_types2 = require("@tradejs/types");
4911
- var import_strategies3 = require("@tradejs/node/strategies");
4918
+ var gateFingerprintRelativePaths = (strategyName) => [
4919
+ `packages/strategies/src/${strategyName}/adapters/ai.ts`,
4920
+ `packages/strategies/src/${strategyName}/guardrails.ts`,
4921
+ `packages/strategies/src/${strategyName}/pockets.ts`,
4922
+ `packages/strategies/src/${strategyName}/config.ts`,
4923
+ "packages/strategies/src/shared/localAiGate.ts",
4924
+ "packages/node/src/ai.ts",
4925
+ "packages/core/src/utils/strategyHelpers/signalBuilders.ts"
4926
+ ];
4927
+ var resolveStrategyGateFingerprint = async ({
4928
+ projectRoot,
4929
+ strategyName,
4930
+ gitSha
4931
+ }) => {
4932
+ const candidates = await Promise.all(
4933
+ gateFingerprintRelativePaths(strategyName).map(async (relativePath) => {
4934
+ const content = await import_promises.default.readFile(import_node_path.default.join(projectRoot, relativePath)).catch(() => null);
4935
+ return content == null ? null : { relativePath, content };
4936
+ })
4937
+ );
4938
+ const sourceEntries = candidates.filter(
4939
+ (entry) => entry != null
4940
+ );
4941
+ const hash = (0, import_node_crypto.createHash)("sha256");
4942
+ hash.update(strategyName);
4943
+ if (sourceEntries.length) {
4944
+ for (const entry of sourceEntries) {
4945
+ hash.update(entry.relativePath);
4946
+ hash.update(entry.content);
4947
+ }
4948
+ } else {
4949
+ hash.update(gitSha ?? "unknown-git-sha");
4950
+ }
4951
+ return {
4952
+ gateFingerprint: hash.digest("hex").slice(0, 16),
4953
+ gateFingerprintFiles: sourceEntries.map((entry) => entry.relativePath)
4954
+ };
4955
+ };
4956
+
4957
+ // src/lib/runtimeLineage.ts
4912
4958
  var RUNTIME_CONTEXT_ENV_KEYS = [
4913
4959
  "AI_MODE",
4914
4960
  "MIN_AI_QUALITY",
@@ -4971,7 +5017,7 @@ var resolveRuntimeMaxLossValue = (config) => {
4971
5017
  );
4972
5018
  return Number.isFinite(maxLossValue) ? maxLossValue : null;
4973
5019
  };
4974
- var fingerprintRuntimeValue = (value) => (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
5020
+ var fingerprintRuntimeValue = (value) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
4975
5021
  var gitLineageCache = /* @__PURE__ */ new Map();
4976
5022
  var gateFingerprintCache = /* @__PURE__ */ new Map();
4977
5023
  var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
@@ -5022,7 +5068,7 @@ var resolveGitLineage = (projectRoot) => {
5022
5068
  };
5023
5069
  var readOptionalFile = async (filePath) => {
5024
5070
  try {
5025
- return await import_promises.default.readFile(filePath);
5071
+ return await import_promises2.default.readFile(filePath);
5026
5072
  } catch {
5027
5073
  return null;
5028
5074
  }
@@ -5032,7 +5078,7 @@ var resolveBinanceBreadthFingerprint = async (projectRoot) => {
5032
5078
  return binanceBreadthFingerprintCache.get(projectRoot) ?? null;
5033
5079
  }
5034
5080
  const content = await readOptionalFile(
5035
- import_node_path.default.join(
5081
+ import_node_path2.default.join(
5036
5082
  projectRoot,
5037
5083
  "packages/node/src/config/binanceBreadthUniverses.json"
5038
5084
  )
@@ -5054,7 +5100,7 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
5054
5100
  if (snapshotFingerprintCache.has(cacheKey)) {
5055
5101
  return snapshotFingerprintCache.get(cacheKey) ?? null;
5056
5102
  }
5057
- const content = await readOptionalFile(import_node_path.default.join(projectRoot, relativePath));
5103
+ const content = await readOptionalFile(import_node_path2.default.join(projectRoot, relativePath));
5058
5104
  let result = null;
5059
5105
  if (content) {
5060
5106
  try {
@@ -5067,55 +5113,26 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
5067
5113
  snapshotFingerprintCache.set(cacheKey, result);
5068
5114
  return result;
5069
5115
  };
5070
- var resolveGateFingerprint = async ({
5116
+ var resolveRuntimeGateFingerprint = async ({
5071
5117
  projectRoot,
5072
5118
  strategyName,
5073
5119
  gitSha
5074
5120
  }) => {
5075
5121
  const cacheKey = `${projectRoot}:${strategyName}:${gitSha ?? "unknown"}`;
5076
5122
  const cached = gateFingerprintCache.get(cacheKey);
5077
- if (cached) {
5078
- return cached;
5079
- }
5080
- const relativeCandidates = [
5081
- `packages/strategies/src/${strategyName}/adapters/ai.ts`,
5082
- `packages/strategies/src/${strategyName}/guardrails.ts`,
5083
- `packages/strategies/src/${strategyName}/pockets.ts`,
5084
- `packages/strategies/src/${strategyName}/config.ts`,
5085
- "packages/node/src/ai.ts",
5086
- "packages/core/src/utils/strategyHelpers/signalBuilders.ts"
5087
- ];
5088
- const optionalEntries = await Promise.all(
5089
- relativeCandidates.map(async (relativePath) => {
5090
- const content = await readOptionalFile(
5091
- import_node_path.default.join(projectRoot, relativePath)
5092
- );
5093
- return content == null ? null : { relativePath, content };
5094
- })
5095
- );
5096
- const sourceEntries = [];
5097
- for (const entry of optionalEntries) {
5098
- if (entry != null) {
5099
- sourceEntries.push(entry);
5100
- }
5101
- }
5102
- const hash = (0, import_node_crypto.createHash)("sha256");
5103
- hash.update(strategyName);
5104
- if (sourceEntries.length) {
5105
- for (const entry of sourceEntries) {
5106
- hash.update(entry.relativePath);
5107
- hash.update(entry.content);
5108
- }
5109
- } else {
5110
- hash.update(gitSha ?? "unknown-git-sha");
5111
- }
5112
- const fingerprint = hash.digest("hex").slice(0, 16);
5113
- gateFingerprintCache.set(cacheKey, fingerprint);
5114
- return fingerprint;
5123
+ if (cached) return cached;
5124
+ const result = await resolveStrategyGateFingerprint({
5125
+ projectRoot,
5126
+ strategyName,
5127
+ gitSha
5128
+ });
5129
+ gateFingerprintCache.set(cacheKey, result);
5130
+ return result;
5115
5131
  };
5116
5132
  var buildRuntimeLineage = async ({
5117
5133
  projectRoot,
5118
5134
  strategyName,
5135
+ compositionId,
5119
5136
  config,
5120
5137
  runContext,
5121
5138
  env = process.env
@@ -5141,19 +5158,23 @@ var buildRuntimeLineage = async ({
5141
5158
  };
5142
5159
  return {
5143
5160
  schemaVersion: 1,
5161
+ compositionId: compositionId?.trim() || null,
5144
5162
  ...git,
5145
- gateFingerprint: await resolveGateFingerprint({
5163
+ gateFingerprint: (await resolveRuntimeGateFingerprint({
5146
5164
  projectRoot,
5147
5165
  strategyName,
5148
5166
  gitSha: git.gitSha
5149
- }),
5150
- configFingerprint: fingerprintRuntimeValue(config),
5167
+ })).gateFingerprint,
5168
+ // Position risk is tracked separately so changing trade size does not
5169
+ // masquerade as a change to the core + gate decision logic.
5170
+ configFingerprint: (0, import_strategyReleaseEvidence.strategyLogicConfigFingerprint)(config),
5151
5171
  contextFingerprint: fingerprintRuntimeValue(context),
5152
5172
  maxLossValue: resolveRuntimeMaxLossValue(config)
5153
5173
  };
5154
5174
  };
5155
5175
  var runtimeLineageKey = (lineage) => [
5156
5176
  lineage.schemaVersion,
5177
+ lineage.compositionId ?? "unbound-composition",
5157
5178
  lineage.gitSha ?? "unknown",
5158
5179
  lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
5159
5180
  lineage.gateFingerprint,
@@ -7166,8 +7187,8 @@ var buildReplayExchangeComparisonDetails = ({
7166
7187
  };
7167
7188
 
7168
7189
  // src/lib/replay/runtimeEvidenceSource.ts
7169
- var import_promises2 = __toESM(require("fs/promises"));
7170
- var import_node_path2 = __toESM(require("path"));
7190
+ var import_promises3 = __toESM(require("fs/promises"));
7191
+ var import_node_path3 = __toESM(require("path"));
7171
7192
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
7172
7193
  var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
7173
7194
  var unwrapRows = (value, field) => (Array.isArray(value) ? value : []).map((item) => {
@@ -7228,9 +7249,9 @@ var loadReplayRuntimeEvidenceSource = async ({
7228
7249
  expectedUserName,
7229
7250
  expectedWindow
7230
7251
  }) => {
7231
- const resolvedPath = import_node_path2.default.resolve(projectRoot, filePath);
7252
+ const resolvedPath = import_node_path3.default.resolve(projectRoot, filePath);
7232
7253
  const root = asRecord(
7233
- JSON.parse(await import_promises2.default.readFile(resolvedPath, "utf8"))
7254
+ JSON.parse(await import_promises3.default.readFile(resolvedPath, "utf8"))
7234
7255
  );
7235
7256
  const runtime = asRecord(root?.runtime) ?? root;
7236
7257
  if (!root || !runtime) {
@@ -8277,8 +8298,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
8277
8298
  };
8278
8299
 
8279
8300
  // src/lib/replay/outputReport.ts
8280
- var import_promises3 = __toESM(require("fs/promises"));
8281
- var import_node_path3 = __toESM(require("path"));
8301
+ var import_promises4 = __toESM(require("fs/promises"));
8302
+ var import_node_path4 = __toESM(require("path"));
8282
8303
  var import_data = require("@tradejs/core/data");
8283
8304
  var formatMsk = (timestamp) => typeof timestamp === "number" && Number.isFinite(timestamp) ? new Intl.DateTimeFormat("ru-RU", {
8284
8305
  timeZone: "Europe/Moscow",
@@ -8582,8 +8603,8 @@ var writeReplayOutputReport = async ({
8582
8603
  strategySnapshot,
8583
8604
  runtimeComparison
8584
8605
  }) => {
8585
- const outputDir = import_node_path3.default.resolve(projectRoot, "data/replay/output");
8586
- await import_promises3.default.mkdir(outputDir, { recursive: true });
8606
+ const outputDir = import_node_path4.default.resolve(projectRoot, "data/replay/output");
8607
+ await import_promises4.default.mkdir(outputDir, { recursive: true });
8587
8608
  const perTradeAnalysis = buildPerTradeAnalysis(runtimeComparison);
8588
8609
  const report = {
8589
8610
  reportType: "replay-output",
@@ -8618,8 +8639,8 @@ var writeReplayOutputReport = async ({
8618
8639
  runtimeComparison,
8619
8640
  perTradeAnalysis
8620
8641
  };
8621
- const markdownPath = import_node_path3.default.join(outputDir, `${timestamp}-replay-report.md`);
8622
- const jsonPath = import_node_path3.default.join(outputDir, `${timestamp}-replay-report.json`);
8642
+ const markdownPath = import_node_path4.default.join(outputDir, `${timestamp}-replay-report.md`);
8643
+ const jsonPath = import_node_path4.default.join(outputDir, `${timestamp}-replay-report.json`);
8623
8644
  const lines = [
8624
8645
  "# Replay Report",
8625
8646
  "",
@@ -8685,8 +8706,8 @@ var writeReplayOutputReport = async ({
8685
8706
  ""
8686
8707
  ];
8687
8708
  await Promise.all([
8688
- import_promises3.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
8689
- import_promises3.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
8709
+ import_promises4.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
8710
+ import_promises4.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
8690
8711
  `, "utf8")
8691
8712
  ]);
8692
8713
  return {