@tradejs/cli 3.1.29-beta.265 → 3.1.29-beta.268

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.
package/dist/cli.js CHANGED
@@ -16947,18 +16947,43 @@ var init_chartSnapshot = __esm({
16947
16947
  });
16948
16948
 
16949
16949
  // src/lib/runtimeParity.ts
16950
- var toFiniteNumberOrNull2, toGroupKey, toRuntimeDuplicateKey, getBacktestParityComparisonTimestamp, sortTradeParityEntries, buildPriceDeltaPct, toExitType, resolveBacktestExitType, extractBacktestEntryParityEntries, extractRuntimeParityEntries, findRuntimeDuplicateGroups, dedupeRuntimeParityEntries, compareTradeParityEntries, summarizeMatchedParity;
16950
+ var resolveTradeParityTimestampOffset, buildTradeParityTimestampOffset, toFiniteNumberOrNull2, toGroupKey, toRuntimeDuplicateKey, getBacktestParityComparisonTimestamp, sortTradeParityEntries, buildPriceDeltaPct, toExitType, resolveBacktestExitType, extractBacktestEntryParityEntries, extractRuntimeParityEntries, findRuntimeDuplicateGroups, dedupeRuntimeParityEntries, compareTradeParityEntries, summarizeMatchedParity;
16951
16951
  var init_runtimeParity = __esm({
16952
16952
  "src/lib/runtimeParity.ts"() {
16953
16953
  "use strict";
16954
+ resolveTradeParityTimestampOffset = (strategy, offset) => typeof offset === "function" ? offset(strategy) : offset;
16955
+ buildTradeParityTimestampOffset = (strategies) => {
16956
+ const intervals = /* @__PURE__ */ new Map();
16957
+ for (const { strategyName, interval: interval4 } of strategies) {
16958
+ const minutes = Number(interval4);
16959
+ if (!Number.isFinite(minutes) || minutes <= 0) {
16960
+ throw new Error(`Invalid embedded replay interval for ${strategyName}`);
16961
+ }
16962
+ intervals.set(strategyName, minutes * 60 * 1e3);
16963
+ }
16964
+ return (strategy) => {
16965
+ const offsetMs = intervals.get(strategy);
16966
+ if (offsetMs == null) {
16967
+ throw new Error(`Missing embedded replay interval for ${strategy}`);
16968
+ }
16969
+ return offsetMs;
16970
+ };
16971
+ };
16954
16972
  toFiniteNumberOrNull2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
16955
16973
  toGroupKey = (entry) => `${entry.strategy}::${entry.symbol}::${entry.direction}`;
16956
16974
  toRuntimeDuplicateKey = (entry) => `${toGroupKey(entry)}::${entry.timestamp}`;
16957
16975
  getBacktestParityComparisonTimestamp = (entry, timestampOffsetMs = 0) => {
16958
- if (timestampOffsetMs > 0 && entry.signalTimestamp != null && entry.timestamp >= entry.signalTimestamp + timestampOffsetMs) {
16976
+ const offsetMs = resolveTradeParityTimestampOffset(
16977
+ entry.strategy,
16978
+ timestampOffsetMs
16979
+ );
16980
+ if (!Number.isFinite(offsetMs) || offsetMs < 0) {
16981
+ throw new Error(`Invalid backtest timestamp offset for ${entry.strategy}`);
16982
+ }
16983
+ if (offsetMs > 0 && entry.signalTimestamp != null && entry.timestamp >= entry.signalTimestamp + offsetMs) {
16959
16984
  return entry.timestamp;
16960
16985
  }
16961
- return (entry.signalTimestamp ?? entry.timestamp) + timestampOffsetMs;
16986
+ return (entry.signalTimestamp ?? entry.timestamp) + offsetMs;
16962
16987
  };
16963
16988
  sortTradeParityEntries = (left, right) => left.timestamp - right.timestamp || left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol) || left.direction.localeCompare(right.direction) || left.id.localeCompare(right.id);
16964
16989
  buildPriceDeltaPct = (leftPrice, rightPrice) => {
@@ -18014,9 +18039,13 @@ var init_runtimeParityDetails = __esm({
18014
18039
  totalFee: entry.totalFee ?? null
18015
18040
  });
18016
18041
  toBacktestParityDetail = (entry, timestampOffsetMs = 0) => {
18042
+ const offsetMs = resolveTradeParityTimestampOffset(
18043
+ entry.strategy,
18044
+ timestampOffsetMs
18045
+ );
18017
18046
  const comparisonTimestamp = getBacktestParityComparisonTimestamp(
18018
18047
  entry,
18019
- timestampOffsetMs
18048
+ offsetMs
18020
18049
  );
18021
18050
  return {
18022
18051
  source: "backtest",
@@ -18026,7 +18055,7 @@ var init_runtimeParityDetails = __esm({
18026
18055
  qty: entry.qty ?? null,
18027
18056
  timestamp: entry.timestamp,
18028
18057
  signalTimestamp: entry.signalTimestamp ?? null,
18029
- comparisonTimestamp: timestampOffsetMs === 0 ? null : comparisonTimestamp,
18058
+ comparisonTimestamp: offsetMs === 0 ? null : comparisonTimestamp,
18030
18059
  price: entry.price,
18031
18060
  exitType: entry.exitType ?? null,
18032
18061
  exitTimestamp: entry.exitTimestamp ?? null,
@@ -18485,25 +18514,29 @@ var init_runtimeParityDetails = __esm({
18485
18514
  let runtimeOnlyArtifactsIncluded = 0;
18486
18515
  let backtestOnlyArtifactsIncluded = 0;
18487
18516
  const runtimeOnlyDiagnostics = runtimeOnly.map((entry, index) => {
18517
+ const entryOffsetMs = resolveTradeParityTimestampOffset(
18518
+ getEntryStrategy(entry) ?? "",
18519
+ backtestTimestampOffsetMs
18520
+ );
18488
18521
  const includeArtifacts = index < artifactLimit;
18489
18522
  const nearestCandidate = nearestByEntry.get(entry);
18490
18523
  const nearestReplaySignal = findNearestSignal({
18491
18524
  entry,
18492
18525
  signals: replaySignals,
18493
18526
  toleranceMs,
18494
- timestampOffsetMs: backtestTimestampOffsetMs
18527
+ timestampOffsetMs: entryOffsetMs
18495
18528
  });
18496
18529
  const nearestReplayEvaluation = findNearestEvaluation({
18497
18530
  entry,
18498
18531
  evaluations: replaySignalEvaluations,
18499
18532
  toleranceMs,
18500
- timestampOffsetMs: backtestTimestampOffsetMs
18533
+ timestampOffsetMs: entryOffsetMs
18501
18534
  });
18502
18535
  const nearestReplayEvaluationOutcome = findNearestReplayEvaluationOutcome({
18503
18536
  entry,
18504
18537
  lineageScopes: replayLineageScopes,
18505
18538
  toleranceMs,
18506
- timestampOffsetMs: backtestTimestampOffsetMs
18539
+ timestampOffsetMs: entryOffsetMs
18507
18540
  });
18508
18541
  const classification = nearestReplaySignal ? classifySignalDiagnostic(nearestReplaySignal.signal) : nearestReplayEvaluation ? classifyEvaluationDiagnostic(nearestReplayEvaluation.evaluation) : nearestReplayEvaluationOutcome ? {
18509
18542
  classification: "core_skipped",
@@ -19386,6 +19419,7 @@ var init_runtimeComparison = __esm({
19386
19419
  init_cliConfig2();
19387
19420
  init_runtimeEvidenceSource();
19388
19421
  init_support();
19422
+ init_runtimeEvidenceDeployment();
19389
19423
  init_runtimeComparisonCalculations();
19390
19424
  init_runtimeComparisonReporting();
19391
19425
  init_runtimeComparisonCalculations();
@@ -19775,6 +19809,9 @@ var init_runtimeComparison = __esm({
19775
19809
  expectedUserName: replayUserName,
19776
19810
  expectedWindow: window
19777
19811
  }) : null;
19812
+ const replayTimestampOffset = runtimeEvidence2 ? buildTradeParityTimestampOffset(
19813
+ (0, runtimeEvidenceDeployment_exports.activeRuntimeEvidenceStrategies)(runtimeEvidence2.deployment)
19814
+ ) : getReplayEntryTimestampCompareOffsetMs();
19778
19815
  const [
19779
19816
  rawRuntimeTrades,
19780
19817
  runtimeSignals,
@@ -19884,7 +19921,7 @@ var init_runtimeComparison = __esm({
19884
19921
  runtimeEntries: runtimeDedupe.entries,
19885
19922
  backtestEntries: comparableBacktestEntries,
19886
19923
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
19887
- backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
19924
+ backtestTimestampOffsetMs: replayTimestampOffset
19888
19925
  });
19889
19926
  const details = buildReplayRuntimeComparisonDetails({
19890
19927
  matched: comparison.matched,
@@ -19893,7 +19930,7 @@ var init_runtimeComparison = __esm({
19893
19930
  runtimeEntries: runtimeDedupe.entries,
19894
19931
  backtestEntries: comparableBacktestEntries,
19895
19932
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
19896
- backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs(),
19933
+ backtestTimestampOffsetMs: replayTimestampOffset,
19897
19934
  runtimeSignals: comparable.runtimeSignals,
19898
19935
  runtimeSignalEvaluations: comparable.runtimeSignalEvaluations,
19899
19936
  replaySignals,
@@ -28295,7 +28332,12 @@ var init_artifacts = __esm({
28295
28332
  "controlPnl",
28296
28333
  "candidatePnl",
28297
28334
  "pnlDelta",
28298
- "exitReasonChanged"
28335
+ "controlExitReason",
28336
+ "candidateExitReason",
28337
+ "exitReasonChanged",
28338
+ "controlExitCode",
28339
+ "candidateExitCode",
28340
+ "exitCodeChanged"
28299
28341
  ].map(csv).join(",") + "\n"
28300
28342
  );
28301
28343
  for (const comparison of params.comparisons) {
@@ -28313,7 +28355,12 @@ var init_artifacts = __esm({
28313
28355
  pair.control.netProfit,
28314
28356
  pair.candidate.netProfit,
28315
28357
  pair.pnlDelta,
28316
- pair.exitReasonChanged
28358
+ pair.control.exitReason,
28359
+ pair.candidate.exitReason,
28360
+ pair.exitReasonChanged,
28361
+ pair.control.exitCode,
28362
+ pair.candidate.exitCode,
28363
+ pair.exitCodeChanged
28317
28364
  ].map(csv).join(",") + "\n"
28318
28365
  );
28319
28366
  }
@@ -28958,6 +29005,7 @@ var init_comparison = __esm({
28958
29005
  candidate: candidateTrade,
28959
29006
  pnlDelta: candidateTrade.netProfit - controlTrade.netProfit,
28960
29007
  exitReasonChanged: candidateTrade.exitReason !== controlTrade.exitReason,
29008
+ exitCodeChanged: candidateTrade.exitCode !== controlTrade.exitCode,
28961
29009
  entryTimestampDeltaMs: candidateTrade.entryTimestamp - controlTrade.entryTimestamp,
28962
29010
  exitTimestampDeltaMs: candidateTrade.exitTimestamp - controlTrade.exitTimestamp
28963
29011
  });
@@ -29175,6 +29223,7 @@ var init_dataset2 = __esm({
29175
29223
  "totalSlippageCost"
29176
29224
  ),
29177
29225
  exitReason: result.exitReason,
29226
+ ...result.exitCode ? { exitCode: result.exitCode } : {},
29178
29227
  regime: resolveCoreResearchRegime(row)
29179
29228
  };
29180
29229
  };
@@ -29218,7 +29267,7 @@ var init_dataset2 = __esm({
29218
29267
  }
29219
29268
  const primary = primaryRows[0];
29220
29269
  if (cycle.some(
29221
- (trade) => trade.exitTimestamp !== primary.exitTimestamp || trade.exitReason !== primary.exitReason
29270
+ (trade) => trade.exitTimestamp !== primary.exitTimestamp || trade.exitReason !== primary.exitReason || trade.exitCode !== primary.exitCode
29222
29271
  )) {
29223
29272
  throw new Error(
29224
29273
  `Position cycle ${identity} contains inconsistent final exits`
@@ -29407,7 +29456,7 @@ var init_report = __esm({
29407
29456
  )}</svg><div class="legend">${series.map((entry) => `<span><i style="background:${entry.color}"></i>${escapeHtml4(entry.id)}</span>`).join("")}</div>`;
29408
29457
  };
29409
29458
  comparisonCards = (comparisons) => comparisons.map(
29410
- (comparison) => `<article class="card"><h3>${escapeHtml4(comparison.candidateId)} vs ${escapeHtml4(comparison.controlId)}</h3><p class="verdict ${comparison.selection.passed ? "positive" : "negative"}">${comparison.selection.passed ? "PASS selection" : "REJECT selection"}</p><dl><dt>Matched</dt><dd>${comparison.matched}</dd><dt>Control only</dt><dd>${comparison.controlOnly}</dd><dt>Candidate only</dt><dd>${comparison.candidateOnly}</dd><dt>Identity retained</dt><dd>${format2(comparison.matchedIdentityPctOfControl)}%</dd><dt>Matched \u0394PnL</dt><dd>${format2(comparison.cohorts.ALL.matchedPnlDelta)}</dd><dt>Removed control PnL</dt><dd>${format2(comparison.cohorts.ALL.controlOnlyPnl)}</dd><dt>Added candidate PnL</dt><dd>${format2(comparison.cohorts.ALL.candidateOnlyPnl)}</dd><dt>Aggregate \u0394PnL</dt><dd>${format2(comparison.cohorts.ALL.delta.pnl)}</dd><dt>Aggregate \u0394DD</dt><dd>${format2(comparison.cohorts.ALL.delta.realizedMaxDrawdown)}</dd><dt>Bootstrap P(\u0394&gt;0)</dt><dd>${format2(comparison.bootstrap.probabilityPositive, 4)}</dd><dt>Holm p</dt><dd>${format2(comparison.bootstrap.holmAdjustedPValue, 4)}</dd></dl><p>${comparison.selection.failedRules.length ? escapeHtml4(comparison.selection.failedRules.map((rule) => `${rule.scope}.${rule.metric} ${rule.expected}`).join("; ")) : "All preregistered rules passed."}</p></article>`
29459
+ (comparison) => `<article class="card"><h3>${escapeHtml4(comparison.candidateId)} vs ${escapeHtml4(comparison.controlId)}</h3><p class="verdict ${comparison.selection.passed ? "positive" : "negative"}">${comparison.selection.passed ? "PASS selection" : "REJECT selection"}</p><dl><dt>Matched</dt><dd>${comparison.matched}</dd><dt>Control only</dt><dd>${comparison.controlOnly}</dd><dt>Candidate only</dt><dd>${comparison.candidateOnly}</dd><dt>Identity retained</dt><dd>${format2(comparison.matchedIdentityPctOfControl)}%</dd><dt>Exit reason changes</dt><dd>${comparison.matchedPairs.filter((pair) => pair.exitReasonChanged).length}</dd><dt>Exit code changes</dt><dd>${comparison.matchedPairs.filter((pair) => pair.exitCodeChanged).length}</dd><dt>Matched \u0394PnL</dt><dd>${format2(comparison.cohorts.ALL.matchedPnlDelta)}</dd><dt>Removed control PnL</dt><dd>${format2(comparison.cohorts.ALL.controlOnlyPnl)}</dd><dt>Added candidate PnL</dt><dd>${format2(comparison.cohorts.ALL.candidateOnlyPnl)}</dd><dt>Aggregate \u0394PnL</dt><dd>${format2(comparison.cohorts.ALL.delta.pnl)}</dd><dt>Aggregate \u0394DD</dt><dd>${format2(comparison.cohorts.ALL.delta.realizedMaxDrawdown)}</dd><dt>Bootstrap P(\u0394&gt;0)</dt><dd>${format2(comparison.bootstrap.probabilityPositive, 4)}</dd><dt>Holm p</dt><dd>${format2(comparison.bootstrap.holmAdjustedPValue, 4)}</dd></dl><p>${comparison.selection.failedRules.length ? escapeHtml4(comparison.selection.failedRules.map((rule) => `${rule.scope}.${rule.metric} ${rule.expected}`).join("; ")) : "All preregistered rules passed."}</p></article>`
29411
29460
  ).join("");
29412
29461
  regimeRows = (variants) => {
29413
29462
  const keys = [
@@ -29421,7 +29470,7 @@ var init_report = __esm({
29421
29470
  ).join("");
29422
29471
  };
29423
29472
  traceRows = (variants) => variants.map(
29424
- (variant) => `<tr><td>${escapeHtml4(variant.variant.label)}</td><td>${escapeHtml4(variant.reconciliation.status)}</td><td>${escapeHtml4(JSON.stringify(variant.setupIdentitySources))}</td><td>${escapeHtml4(JSON.stringify(variant.traceFunnel.events))}</td><td>${escapeHtml4(JSON.stringify(variant.traceFunnel.skipCounts))}</td><td>${escapeHtml4(variant.latestSignalTimeRegime?.key ?? "n/a")}</td></tr>`
29473
+ (variant) => `<tr><td>${escapeHtml4(variant.variant.label)}</td><td>${escapeHtml4(variant.reconciliation.status)}</td><td>${escapeHtml4(JSON.stringify(variant.setupIdentitySources))}</td><td>${escapeHtml4(JSON.stringify(variant.traceFunnel.events))}</td><td>${escapeHtml4(JSON.stringify(variant.traceFunnel.skipCounts))}</td><td>${escapeHtml4(JSON.stringify(variant.traceFunnel.exitCodes))}</td><td>${escapeHtml4(variant.latestSignalTimeRegime?.key ?? "n/a")}</td></tr>`
29425
29474
  ).join("");
29426
29475
  buildCoreResearchHtml = (params) => {
29427
29476
  const { spec, result, tradesByVariant } = params;
@@ -29447,7 +29496,7 @@ var init_report = __esm({
29447
29496
  ([key, value]) => `<span class="${value === "present" ? "positive" : "negative"}">${escapeHtml4(key)}: ${value}</span>`
29448
29497
  ).join(
29449
29498
  ""
29450
- )}</section><h2>ALL equity</h2>${buildCurveSvg(tradesByVariant, "ALL", "equity")}<h2>ALL realized drawdown</h2>${buildCurveSvg(tradesByVariant, "ALL", "drawdown")}<h2>LONG equity</h2>${buildCurveSvg(tradesByVariant, "LONG", "equity")}<h2>LONG realized drawdown</h2>${buildCurveSvg(tradesByVariant, "LONG", "drawdown")}<h2>SHORT equity</h2>${buildCurveSvg(tradesByVariant, "SHORT", "equity")}<h2>SHORT realized drawdown</h2>${buildCurveSvg(tradesByVariant, "SHORT", "drawdown")}<h2>Fixed cohort metrics</h2><p class="muted">Avg PnL/trade = cohort PnL / cohort N. ALL drawdown is aggregate portfolio; LONG/SHORT drawdowns are side-only.</p><div class="scroll"><table><thead><tr><th>Variant</th><th>Window</th><th>Cohort</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>WR</th><th>Realized DD</th><th>Cad/day</th><th>Avg win</th><th>Avg loss</th><th>Payoff</th><th>Median</th><th>P05</th><th>P95</th><th>Median hold h</th><th>Max loss streak</th></tr></thead><tbody>${allRows}</tbody></table></div><h2>Fold and monthly robustness</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Window</th><th>Cohort</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>WR</th><th>Realized DD</th><th>Cad/day</th><th>Avg win</th><th>Avg loss</th><th>Payoff</th><th>Median</th><th>P05</th><th>P95</th><th>Median hold h</th><th>Max loss streak</th></tr></thead><tbody>${robustnessRows}</tbody></table></div><h2>Cost stress</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Extra round-trip bps</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>Realized DD</th></tr></thead><tbody>${costRows}</tbody></table></div><h2>Signal-time market regimes</h2><div class="scroll"><table><thead><tr><th>Trend | Volatility | Breadth | Derivatives</th><th>Variant</th><th>N</th><th>PnL</th><th>PF</th><th>WR</th></tr></thead><tbody>${regimeRows(result.variants)}</tbody></table></div><h2>Trace funnel and reconciliation</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Reconcile</th><th>Setup identity source</th><th>Events</th><th>Top skips</th><th>Latest signal-time regime</th></tr></thead><tbody>${traceRows(result.variants)}</tbody></table></div><h2>Multiple testing / overfitting</h2><pre>${escapeHtml4(JSON.stringify({ multipleTesting: result.multipleTesting, overfittingDiagnostics: result.overfittingDiagnostics }, null, 2))}</pre><h2>Lineage</h2><pre>${escapeHtml4(JSON.stringify({ stage: spec.stage, universeSha256: spec.universe.sha256, lineage: spec.lineage ?? null, artifactHashes: result.artifactHashes }, null, 2))}</pre></main></body></html>`;
29499
+ )}</section><h2>ALL equity</h2>${buildCurveSvg(tradesByVariant, "ALL", "equity")}<h2>ALL realized drawdown</h2>${buildCurveSvg(tradesByVariant, "ALL", "drawdown")}<h2>LONG equity</h2>${buildCurveSvg(tradesByVariant, "LONG", "equity")}<h2>LONG realized drawdown</h2>${buildCurveSvg(tradesByVariant, "LONG", "drawdown")}<h2>SHORT equity</h2>${buildCurveSvg(tradesByVariant, "SHORT", "equity")}<h2>SHORT realized drawdown</h2>${buildCurveSvg(tradesByVariant, "SHORT", "drawdown")}<h2>Fixed cohort metrics</h2><p class="muted">Avg PnL/trade = cohort PnL / cohort completed positions. ALL drawdown is aggregate portfolio; LONG/SHORT drawdowns are side-only.</p><div class="scroll"><table><thead><tr><th>Variant</th><th>Window</th><th>Cohort</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>WR</th><th>Realized DD</th><th>Cad/day</th><th>Avg win</th><th>Avg loss</th><th>Payoff</th><th>Median</th><th>P05</th><th>P95</th><th>Median hold h</th><th>Max loss streak</th></tr></thead><tbody>${allRows}</tbody></table></div><h2>Fold and monthly robustness</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Window</th><th>Cohort</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>WR</th><th>Realized DD</th><th>Cad/day</th><th>Avg win</th><th>Avg loss</th><th>Payoff</th><th>Median</th><th>P05</th><th>P95</th><th>Median hold h</th><th>Max loss streak</th></tr></thead><tbody>${robustnessRows}</tbody></table></div><h2>Cost stress</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Extra round-trip bps</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>Realized DD</th></tr></thead><tbody>${costRows}</tbody></table></div><h2>Signal-time market regimes</h2><div class="scroll"><table><thead><tr><th>Trend | Volatility | Breadth | Derivatives</th><th>Variant</th><th>N</th><th>PnL</th><th>PF</th><th>WR</th></tr></thead><tbody>${regimeRows(result.variants)}</tbody></table></div><h2>Trace funnel and reconciliation</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Reconcile</th><th>Setup identity source</th><th>Events</th><th>Top skips</th><th>Exit codes</th><th>Latest signal-time regime</th></tr></thead><tbody>${traceRows(result.variants)}</tbody></table></div><h2>Multiple testing / overfitting</h2><pre>${escapeHtml4(JSON.stringify({ multipleTesting: result.multipleTesting, overfittingDiagnostics: result.overfittingDiagnostics }, null, 2))}</pre><h2>Lineage</h2><pre>${escapeHtml4(JSON.stringify({ stage: spec.stage, universeSha256: spec.universe.sha256, lineage: spec.lineage ?? null, artifactHashes: result.artifactHashes }, null, 2))}</pre></main></body></html>`;
29451
29500
  };
29452
29501
  }
29453
29502
  });
@@ -29749,8 +29798,9 @@ var init_trace = __esm({
29749
29798
  import_promises24 = __toESM(require("fs/promises"));
29750
29799
  import_node_path18 = __toESM(require("path"));
29751
29800
  summarizeCoreResearchTrace = async (filePaths = []) => {
29752
- const events = {};
29753
- const skipCounts = {};
29801
+ const events = /* @__PURE__ */ Object.create(null);
29802
+ const skipCounts = /* @__PURE__ */ Object.create(null);
29803
+ const exitCodes = /* @__PURE__ */ Object.create(null);
29754
29804
  for (const inputPath of filePaths) {
29755
29805
  const filePath = import_node_path18.default.resolve(inputPath);
29756
29806
  const text = await import_promises24.default.readFile(filePath, "utf8");
@@ -29770,6 +29820,9 @@ var init_trace = __esm({
29770
29820
  skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
29771
29821
  }
29772
29822
  }
29823
+ if (event.event === "position_exited" && event.exitCode) {
29824
+ exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
29825
+ }
29773
29826
  }
29774
29827
  }
29775
29828
  return {
@@ -29778,6 +29831,11 @@ var init_trace = __esm({
29778
29831
  Object.entries(skipCounts).sort(
29779
29832
  ([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
29780
29833
  )
29834
+ ),
29835
+ exitCodes: Object.fromEntries(
29836
+ Object.entries(exitCodes).sort(
29837
+ ([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
29838
+ )
29781
29839
  )
29782
29840
  };
29783
29841
  };
@@ -31772,41 +31830,52 @@ var init_runtimeFeedbackReplay = __esm({
31772
31830
  interval: interval4,
31773
31831
  startTime,
31774
31832
  endTime
31775
- }) => [
31776
- [
31777
- cliPath,
31778
- "replay",
31779
- "--user",
31780
- userName3,
31781
- "--connector",
31782
- connectorName,
31783
- "--deployment",
31784
- deploymentId,
31785
- "--timeframe",
31786
- interval4,
31787
- "--startTime",
31788
- String(startTime),
31789
- "--endTime",
31790
- String(endTime),
31791
- "--runtimeEvidence",
31792
- runtimeEvidencePath,
31793
- "--cacheOnly"
31794
- ],
31795
- [
31796
- cliPath,
31797
- "replay-runtime-evidence",
31798
- "--user",
31799
- userName3,
31800
- "--runtimeEvidence",
31801
- runtimeEvidencePath,
31802
- "--startTime",
31803
- String(startTime),
31804
- "--endTime",
31805
- String(endTime),
31806
- "--out",
31807
- replayEvidencePath
31808
- ]
31809
- ];
31833
+ }) => {
31834
+ const intervals = interval4.split(",").map((value) => value.trim());
31835
+ if (!intervals.length || intervals.some(
31836
+ (value) => !value || !Number.isFinite(Number(value)) || Number(value) <= 0
31837
+ )) {
31838
+ throw new Error(`Invalid runtime feedback replay intervals: ${interval4}`);
31839
+ }
31840
+ const replayTimeframe = intervals.sort(
31841
+ (left, right) => Number(left) - Number(right)
31842
+ )[0];
31843
+ return [
31844
+ [
31845
+ cliPath,
31846
+ "replay",
31847
+ "--user",
31848
+ userName3,
31849
+ "--connector",
31850
+ connectorName,
31851
+ "--deployment",
31852
+ deploymentId,
31853
+ "--timeframe",
31854
+ replayTimeframe,
31855
+ "--startTime",
31856
+ String(startTime),
31857
+ "--endTime",
31858
+ String(endTime),
31859
+ "--runtimeEvidence",
31860
+ runtimeEvidencePath,
31861
+ "--cacheOnly"
31862
+ ],
31863
+ [
31864
+ cliPath,
31865
+ "replay-runtime-evidence",
31866
+ "--user",
31867
+ userName3,
31868
+ "--runtimeEvidence",
31869
+ runtimeEvidencePath,
31870
+ "--startTime",
31871
+ String(startTime),
31872
+ "--endTime",
31873
+ String(endTime),
31874
+ "--out",
31875
+ replayEvidencePath
31876
+ ]
31877
+ ];
31878
+ };
31810
31879
  runLoggedCommand = async ({
31811
31880
  executable,
31812
31881
  command,
@@ -31893,10 +31962,8 @@ $ ${[executable, ...command].join(" ")}
31893
31962
  const intervals = new Set(
31894
31963
  activeStrategies.map(({ interval: interval4 }) => String(interval4))
31895
31964
  );
31896
- if (intervals.size !== 1) {
31897
- throw new Error(
31898
- `Runtime feedback replay requires one interval, received ${[...intervals].join(",")}`
31899
- );
31965
+ if (!intervals.size) {
31966
+ throw new Error("Runtime feedback replay requires an active interval");
31900
31967
  }
31901
31968
  await import_promises28.default.mkdir(outDir, { recursive: true });
31902
31969
  const existing = await import_promises28.default.readdir(outDir);
@@ -31917,7 +31984,7 @@ $ ${[executable, ...command].join(" ")}
31917
31984
  userName: runtimeEvidenceBundle.manifest.userName,
31918
31985
  connectorName: deployment.connectorName,
31919
31986
  deploymentId: deployment.id,
31920
- interval: [...intervals][0],
31987
+ interval: [...intervals].join(","),
31921
31988
  startTime: runtimeEvidenceBundle.manifest.window.startTime,
31922
31989
  endTime: runtimeEvidenceBundle.manifest.window.endTime
31923
31990
  });
@@ -691,7 +691,12 @@ var writeCoreResearchMatches = async (params) => {
691
691
  "controlPnl",
692
692
  "candidatePnl",
693
693
  "pnlDelta",
694
- "exitReasonChanged"
694
+ "controlExitReason",
695
+ "candidateExitReason",
696
+ "exitReasonChanged",
697
+ "controlExitCode",
698
+ "candidateExitCode",
699
+ "exitCodeChanged"
695
700
  ].map(csv).join(",") + "\n"
696
701
  );
697
702
  for (const comparison of params.comparisons) {
@@ -709,7 +714,12 @@ var writeCoreResearchMatches = async (params) => {
709
714
  pair.control.netProfit,
710
715
  pair.candidate.netProfit,
711
716
  pair.pnlDelta,
712
- pair.exitReasonChanged
717
+ pair.control.exitReason,
718
+ pair.candidate.exitReason,
719
+ pair.exitReasonChanged,
720
+ pair.control.exitCode,
721
+ pair.candidate.exitCode,
722
+ pair.exitCodeChanged
713
723
  ].map(csv).join(",") + "\n"
714
724
  );
715
725
  }
@@ -1326,6 +1336,7 @@ var compareCoreResearchVariants = (params) => {
1326
1336
  candidate: candidateTrade,
1327
1337
  pnlDelta: candidateTrade.netProfit - controlTrade.netProfit,
1328
1338
  exitReasonChanged: candidateTrade.exitReason !== controlTrade.exitReason,
1339
+ exitCodeChanged: candidateTrade.exitCode !== controlTrade.exitCode,
1329
1340
  entryTimestampDeltaMs: candidateTrade.entryTimestamp - controlTrade.entryTimestamp,
1330
1341
  exitTimestampDeltaMs: candidateTrade.exitTimestamp - controlTrade.exitTimestamp
1331
1342
  });
@@ -1537,6 +1548,7 @@ var normalizeCompletedTrade = (params) => {
1537
1548
  "totalSlippageCost"
1538
1549
  ),
1539
1550
  exitReason: result.exitReason,
1551
+ ...result.exitCode ? { exitCode: result.exitCode } : {},
1540
1552
  regime: resolveCoreResearchRegime(row)
1541
1553
  };
1542
1554
  };
@@ -1580,7 +1592,7 @@ var aggregatePositionCycles = (trades) => {
1580
1592
  }
1581
1593
  const primary = primaryRows[0];
1582
1594
  if (cycle.some(
1583
- (trade) => trade.exitTimestamp !== primary.exitTimestamp || trade.exitReason !== primary.exitReason
1595
+ (trade) => trade.exitTimestamp !== primary.exitTimestamp || trade.exitReason !== primary.exitReason || trade.exitCode !== primary.exitCode
1584
1596
  )) {
1585
1597
  throw new Error(
1586
1598
  `Position cycle ${identity} contains inconsistent final exits`
@@ -1762,7 +1774,7 @@ var buildCurveSvg = (tradesByVariant, cohort, curve) => {
1762
1774
  )}</svg><div class="legend">${series.map((entry) => `<span><i style="background:${entry.color}"></i>${escapeHtml(entry.id)}</span>`).join("")}</div>`;
1763
1775
  };
1764
1776
  var comparisonCards = (comparisons) => comparisons.map(
1765
- (comparison) => `<article class="card"><h3>${escapeHtml(comparison.candidateId)} vs ${escapeHtml(comparison.controlId)}</h3><p class="verdict ${comparison.selection.passed ? "positive" : "negative"}">${comparison.selection.passed ? "PASS selection" : "REJECT selection"}</p><dl><dt>Matched</dt><dd>${comparison.matched}</dd><dt>Control only</dt><dd>${comparison.controlOnly}</dd><dt>Candidate only</dt><dd>${comparison.candidateOnly}</dd><dt>Identity retained</dt><dd>${format(comparison.matchedIdentityPctOfControl)}%</dd><dt>Matched \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.matchedPnlDelta)}</dd><dt>Removed control PnL</dt><dd>${format(comparison.cohorts.ALL.controlOnlyPnl)}</dd><dt>Added candidate PnL</dt><dd>${format(comparison.cohorts.ALL.candidateOnlyPnl)}</dd><dt>Aggregate \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.delta.pnl)}</dd><dt>Aggregate \u0394DD</dt><dd>${format(comparison.cohorts.ALL.delta.realizedMaxDrawdown)}</dd><dt>Bootstrap P(\u0394&gt;0)</dt><dd>${format(comparison.bootstrap.probabilityPositive, 4)}</dd><dt>Holm p</dt><dd>${format(comparison.bootstrap.holmAdjustedPValue, 4)}</dd></dl><p>${comparison.selection.failedRules.length ? escapeHtml(comparison.selection.failedRules.map((rule) => `${rule.scope}.${rule.metric} ${rule.expected}`).join("; ")) : "All preregistered rules passed."}</p></article>`
1777
+ (comparison) => `<article class="card"><h3>${escapeHtml(comparison.candidateId)} vs ${escapeHtml(comparison.controlId)}</h3><p class="verdict ${comparison.selection.passed ? "positive" : "negative"}">${comparison.selection.passed ? "PASS selection" : "REJECT selection"}</p><dl><dt>Matched</dt><dd>${comparison.matched}</dd><dt>Control only</dt><dd>${comparison.controlOnly}</dd><dt>Candidate only</dt><dd>${comparison.candidateOnly}</dd><dt>Identity retained</dt><dd>${format(comparison.matchedIdentityPctOfControl)}%</dd><dt>Exit reason changes</dt><dd>${comparison.matchedPairs.filter((pair) => pair.exitReasonChanged).length}</dd><dt>Exit code changes</dt><dd>${comparison.matchedPairs.filter((pair) => pair.exitCodeChanged).length}</dd><dt>Matched \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.matchedPnlDelta)}</dd><dt>Removed control PnL</dt><dd>${format(comparison.cohorts.ALL.controlOnlyPnl)}</dd><dt>Added candidate PnL</dt><dd>${format(comparison.cohorts.ALL.candidateOnlyPnl)}</dd><dt>Aggregate \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.delta.pnl)}</dd><dt>Aggregate \u0394DD</dt><dd>${format(comparison.cohorts.ALL.delta.realizedMaxDrawdown)}</dd><dt>Bootstrap P(\u0394&gt;0)</dt><dd>${format(comparison.bootstrap.probabilityPositive, 4)}</dd><dt>Holm p</dt><dd>${format(comparison.bootstrap.holmAdjustedPValue, 4)}</dd></dl><p>${comparison.selection.failedRules.length ? escapeHtml(comparison.selection.failedRules.map((rule) => `${rule.scope}.${rule.metric} ${rule.expected}`).join("; ")) : "All preregistered rules passed."}</p></article>`
1766
1778
  ).join("");
1767
1779
  var regimeRows = (variants) => {
1768
1780
  const keys = [
@@ -1776,7 +1788,7 @@ var regimeRows = (variants) => {
1776
1788
  ).join("");
1777
1789
  };
1778
1790
  var traceRows = (variants) => variants.map(
1779
- (variant) => `<tr><td>${escapeHtml(variant.variant.label)}</td><td>${escapeHtml(variant.reconciliation.status)}</td><td>${escapeHtml(JSON.stringify(variant.setupIdentitySources))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.events))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.skipCounts))}</td><td>${escapeHtml(variant.latestSignalTimeRegime?.key ?? "n/a")}</td></tr>`
1791
+ (variant) => `<tr><td>${escapeHtml(variant.variant.label)}</td><td>${escapeHtml(variant.reconciliation.status)}</td><td>${escapeHtml(JSON.stringify(variant.setupIdentitySources))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.events))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.skipCounts))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.exitCodes))}</td><td>${escapeHtml(variant.latestSignalTimeRegime?.key ?? "n/a")}</td></tr>`
1780
1792
  ).join("");
1781
1793
  var buildCoreResearchHtml = (params) => {
1782
1794
  const { spec, result, tradesByVariant } = params;
@@ -1802,7 +1814,7 @@ var buildCoreResearchHtml = (params) => {
1802
1814
  ([key, value]) => `<span class="${value === "present" ? "positive" : "negative"}">${escapeHtml(key)}: ${value}</span>`
1803
1815
  ).join(
1804
1816
  ""
1805
- )}</section><h2>ALL equity</h2>${buildCurveSvg(tradesByVariant, "ALL", "equity")}<h2>ALL realized drawdown</h2>${buildCurveSvg(tradesByVariant, "ALL", "drawdown")}<h2>LONG equity</h2>${buildCurveSvg(tradesByVariant, "LONG", "equity")}<h2>LONG realized drawdown</h2>${buildCurveSvg(tradesByVariant, "LONG", "drawdown")}<h2>SHORT equity</h2>${buildCurveSvg(tradesByVariant, "SHORT", "equity")}<h2>SHORT realized drawdown</h2>${buildCurveSvg(tradesByVariant, "SHORT", "drawdown")}<h2>Fixed cohort metrics</h2><p class="muted">Avg PnL/trade = cohort PnL / cohort N. ALL drawdown is aggregate portfolio; LONG/SHORT drawdowns are side-only.</p><div class="scroll"><table><thead><tr><th>Variant</th><th>Window</th><th>Cohort</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>WR</th><th>Realized DD</th><th>Cad/day</th><th>Avg win</th><th>Avg loss</th><th>Payoff</th><th>Median</th><th>P05</th><th>P95</th><th>Median hold h</th><th>Max loss streak</th></tr></thead><tbody>${allRows}</tbody></table></div><h2>Fold and monthly robustness</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Window</th><th>Cohort</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>WR</th><th>Realized DD</th><th>Cad/day</th><th>Avg win</th><th>Avg loss</th><th>Payoff</th><th>Median</th><th>P05</th><th>P95</th><th>Median hold h</th><th>Max loss streak</th></tr></thead><tbody>${robustnessRows}</tbody></table></div><h2>Cost stress</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Extra round-trip bps</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>Realized DD</th></tr></thead><tbody>${costRows}</tbody></table></div><h2>Signal-time market regimes</h2><div class="scroll"><table><thead><tr><th>Trend | Volatility | Breadth | Derivatives</th><th>Variant</th><th>N</th><th>PnL</th><th>PF</th><th>WR</th></tr></thead><tbody>${regimeRows(result.variants)}</tbody></table></div><h2>Trace funnel and reconciliation</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Reconcile</th><th>Setup identity source</th><th>Events</th><th>Top skips</th><th>Latest signal-time regime</th></tr></thead><tbody>${traceRows(result.variants)}</tbody></table></div><h2>Multiple testing / overfitting</h2><pre>${escapeHtml(JSON.stringify({ multipleTesting: result.multipleTesting, overfittingDiagnostics: result.overfittingDiagnostics }, null, 2))}</pre><h2>Lineage</h2><pre>${escapeHtml(JSON.stringify({ stage: spec.stage, universeSha256: spec.universe.sha256, lineage: spec.lineage ?? null, artifactHashes: result.artifactHashes }, null, 2))}</pre></main></body></html>`;
1817
+ )}</section><h2>ALL equity</h2>${buildCurveSvg(tradesByVariant, "ALL", "equity")}<h2>ALL realized drawdown</h2>${buildCurveSvg(tradesByVariant, "ALL", "drawdown")}<h2>LONG equity</h2>${buildCurveSvg(tradesByVariant, "LONG", "equity")}<h2>LONG realized drawdown</h2>${buildCurveSvg(tradesByVariant, "LONG", "drawdown")}<h2>SHORT equity</h2>${buildCurveSvg(tradesByVariant, "SHORT", "equity")}<h2>SHORT realized drawdown</h2>${buildCurveSvg(tradesByVariant, "SHORT", "drawdown")}<h2>Fixed cohort metrics</h2><p class="muted">Avg PnL/trade = cohort PnL / cohort completed positions. ALL drawdown is aggregate portfolio; LONG/SHORT drawdowns are side-only.</p><div class="scroll"><table><thead><tr><th>Variant</th><th>Window</th><th>Cohort</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>WR</th><th>Realized DD</th><th>Cad/day</th><th>Avg win</th><th>Avg loss</th><th>Payoff</th><th>Median</th><th>P05</th><th>P95</th><th>Median hold h</th><th>Max loss streak</th></tr></thead><tbody>${allRows}</tbody></table></div><h2>Fold and monthly robustness</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Window</th><th>Cohort</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>WR</th><th>Realized DD</th><th>Cad/day</th><th>Avg win</th><th>Avg loss</th><th>Payoff</th><th>Median</th><th>P05</th><th>P95</th><th>Median hold h</th><th>Max loss streak</th></tr></thead><tbody>${robustnessRows}</tbody></table></div><h2>Cost stress</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Extra round-trip bps</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>Realized DD</th></tr></thead><tbody>${costRows}</tbody></table></div><h2>Signal-time market regimes</h2><div class="scroll"><table><thead><tr><th>Trend | Volatility | Breadth | Derivatives</th><th>Variant</th><th>N</th><th>PnL</th><th>PF</th><th>WR</th></tr></thead><tbody>${regimeRows(result.variants)}</tbody></table></div><h2>Trace funnel and reconciliation</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Reconcile</th><th>Setup identity source</th><th>Events</th><th>Top skips</th><th>Exit codes</th><th>Latest signal-time regime</th></tr></thead><tbody>${traceRows(result.variants)}</tbody></table></div><h2>Multiple testing / overfitting</h2><pre>${escapeHtml(JSON.stringify({ multipleTesting: result.multipleTesting, overfittingDiagnostics: result.overfittingDiagnostics }, null, 2))}</pre><h2>Lineage</h2><pre>${escapeHtml(JSON.stringify({ stage: spec.stage, universeSha256: spec.universe.sha256, lineage: spec.lineage ?? null, artifactHashes: result.artifactHashes }, null, 2))}</pre></main></body></html>`;
1806
1818
  };
1807
1819
 
1808
1820
  // src/lib/backtest/checkpoint.ts
@@ -2108,8 +2120,9 @@ var applyCoreResearchRobustnessGuardrails = (params) => {
2108
2120
  var import_promises3 = __toESM(require("fs/promises"));
2109
2121
  var import_node_path4 = __toESM(require("path"));
2110
2122
  var summarizeCoreResearchTrace = async (filePaths = []) => {
2111
- const events = {};
2112
- const skipCounts = {};
2123
+ const events = /* @__PURE__ */ Object.create(null);
2124
+ const skipCounts = /* @__PURE__ */ Object.create(null);
2125
+ const exitCodes = /* @__PURE__ */ Object.create(null);
2113
2126
  for (const inputPath of filePaths) {
2114
2127
  const filePath = import_node_path4.default.resolve(inputPath);
2115
2128
  const text = await import_promises3.default.readFile(filePath, "utf8");
@@ -2129,6 +2142,9 @@ var summarizeCoreResearchTrace = async (filePaths = []) => {
2129
2142
  skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
2130
2143
  }
2131
2144
  }
2145
+ if (event.event === "position_exited" && event.exitCode) {
2146
+ exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
2147
+ }
2132
2148
  }
2133
2149
  }
2134
2150
  return {
@@ -2137,6 +2153,11 @@ var summarizeCoreResearchTrace = async (filePaths = []) => {
2137
2153
  Object.entries(skipCounts).sort(
2138
2154
  ([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
2139
2155
  )
2156
+ ),
2157
+ exitCodes: Object.fromEntries(
2158
+ Object.entries(exitCodes).sort(
2159
+ ([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
2160
+ )
2140
2161
  )
2141
2162
  };
2142
2163
  };
@@ -133,41 +133,52 @@ var buildRuntimeFeedbackReplayCommands = ({
133
133
  interval,
134
134
  startTime,
135
135
  endTime
136
- }) => [
137
- [
138
- cliPath,
139
- "replay",
140
- "--user",
141
- userName,
142
- "--connector",
143
- connectorName,
144
- "--deployment",
145
- deploymentId,
146
- "--timeframe",
147
- interval,
148
- "--startTime",
149
- String(startTime),
150
- "--endTime",
151
- String(endTime),
152
- "--runtimeEvidence",
153
- runtimeEvidencePath,
154
- "--cacheOnly"
155
- ],
156
- [
157
- cliPath,
158
- "replay-runtime-evidence",
159
- "--user",
160
- userName,
161
- "--runtimeEvidence",
162
- runtimeEvidencePath,
163
- "--startTime",
164
- String(startTime),
165
- "--endTime",
166
- String(endTime),
167
- "--out",
168
- replayEvidencePath
169
- ]
170
- ];
136
+ }) => {
137
+ const intervals = interval.split(",").map((value) => value.trim());
138
+ if (!intervals.length || intervals.some(
139
+ (value) => !value || !Number.isFinite(Number(value)) || Number(value) <= 0
140
+ )) {
141
+ throw new Error(`Invalid runtime feedback replay intervals: ${interval}`);
142
+ }
143
+ const replayTimeframe = intervals.sort(
144
+ (left, right) => Number(left) - Number(right)
145
+ )[0];
146
+ return [
147
+ [
148
+ cliPath,
149
+ "replay",
150
+ "--user",
151
+ userName,
152
+ "--connector",
153
+ connectorName,
154
+ "--deployment",
155
+ deploymentId,
156
+ "--timeframe",
157
+ replayTimeframe,
158
+ "--startTime",
159
+ String(startTime),
160
+ "--endTime",
161
+ String(endTime),
162
+ "--runtimeEvidence",
163
+ runtimeEvidencePath,
164
+ "--cacheOnly"
165
+ ],
166
+ [
167
+ cliPath,
168
+ "replay-runtime-evidence",
169
+ "--user",
170
+ userName,
171
+ "--runtimeEvidence",
172
+ runtimeEvidencePath,
173
+ "--startTime",
174
+ String(startTime),
175
+ "--endTime",
176
+ String(endTime),
177
+ "--out",
178
+ replayEvidencePath
179
+ ]
180
+ ];
181
+ };
171
182
  var runLoggedCommand = async ({
172
183
  executable,
173
184
  command,
@@ -254,10 +265,8 @@ var runRuntimeFeedbackReplay = async ({
254
265
  const intervals = new Set(
255
266
  activeStrategies.map(({ interval }) => String(interval))
256
267
  );
257
- if (intervals.size !== 1) {
258
- throw new Error(
259
- `Runtime feedback replay requires one interval, received ${[...intervals].join(",")}`
260
- );
268
+ if (!intervals.size) {
269
+ throw new Error("Runtime feedback replay requires an active interval");
261
270
  }
262
271
  await import_promises.default.mkdir(outDir, { recursive: true });
263
272
  const existing = await import_promises.default.readdir(outDir);
@@ -278,7 +287,7 @@ var runRuntimeFeedbackReplay = async ({
278
287
  userName: runtimeEvidenceBundle.manifest.userName,
279
288
  connectorName: deployment.connectorName,
280
289
  deploymentId: deployment.id,
281
- interval: [...intervals][0],
290
+ interval: [...intervals].join(","),
282
291
  startTime: runtimeEvidenceBundle.manifest.window.startTime,
283
292
  endTime: runtimeEvidenceBundle.manifest.window.endTime
284
293
  });