@tradejs/cli 3.1.16 → 3.1.17-beta.231

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.
@@ -114,9 +114,7 @@ var loadRuntimeTrades = async (userName, {
114
114
  dedupedBucketTrades.set(trade.orderId, trade);
115
115
  }
116
116
  const windowedTrades = [...dedupedBucketTrades.values()].filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
117
- if (windowedTrades.length > 0 || dayKeys.length === 0) {
118
- return windowedTrades;
119
- }
117
+ return windowedTrades;
120
118
  }
121
119
  const tradePrefix = import_redis.redisKeys.runtimeTrades(userName);
122
120
  const bucketPrefix = import_redis.redisKeys.runtimeTradeBuckets(userName);
@@ -2709,27 +2707,27 @@ var createCoinMarketCapFetchStats = () => ({
2709
2707
  totalCredits: 0,
2710
2708
  byPath: /* @__PURE__ */ new Map()
2711
2709
  });
2712
- var getCoinMarketCapPathLabel = (path5) => {
2713
- if (path5.includes("global-metrics")) return "global";
2714
- if (path5.includes("cryptocurrency/quotes")) return "reference";
2715
- if (path5.includes("exchange/quotes")) return "exchangeLiquidity";
2716
- if (path5.includes("fear-and-greed")) return "fearGreed";
2717
- if (path5.includes("cmc100")) return "cmc100";
2718
- if (path5.includes("cmc20")) return "cmc20";
2719
- return path5;
2720
- };
2721
- var recordCoinMarketCapFetchStats = (stats, path5, creditCount) => {
2710
+ var getCoinMarketCapPathLabel = (path3) => {
2711
+ if (path3.includes("global-metrics")) return "global";
2712
+ if (path3.includes("cryptocurrency/quotes")) return "reference";
2713
+ if (path3.includes("exchange/quotes")) return "exchangeLiquidity";
2714
+ if (path3.includes("fear-and-greed")) return "fearGreed";
2715
+ if (path3.includes("cmc100")) return "cmc100";
2716
+ if (path3.includes("cmc20")) return "cmc20";
2717
+ return path3;
2718
+ };
2719
+ var recordCoinMarketCapFetchStats = (stats, path3, creditCount) => {
2722
2720
  if (!stats) return;
2723
- const existing = stats.byPath.get(path5) ?? { requests: 0, credits: 0 };
2721
+ const existing = stats.byPath.get(path3) ?? { requests: 0, credits: 0 };
2724
2722
  existing.requests += 1;
2725
2723
  existing.credits += creditCount ?? 0;
2726
2724
  stats.requests += 1;
2727
2725
  stats.totalCredits += creditCount ?? 0;
2728
- stats.byPath.set(path5, existing);
2726
+ stats.byPath.set(path3, existing);
2729
2727
  };
2730
2728
  var formatCoinMarketCapFetchStats = (stats) => {
2731
2729
  const byPath = [...stats.byPath.entries()].map(
2732
- ([path5, item]) => `${getCoinMarketCapPathLabel(path5)}=${item.requests}req/${item.credits}cr`
2730
+ ([path3, item]) => `${getCoinMarketCapPathLabel(path3)}=${item.requests}req/${item.credits}cr`
2733
2731
  ).join(" ");
2734
2732
  return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
2735
2733
  };
@@ -4350,109 +4348,11 @@ var replayDeploymentId = String(
4350
4348
  var isReplayUpdateOnlyRun = Boolean(replayFlags.updateOnly);
4351
4349
 
4352
4350
  // src/lib/replay/runtimeEvidenceSource.ts
4353
- var import_promises3 = __toESM(require("fs/promises"));
4354
- var import_node_path3 = __toESM(require("path"));
4355
-
4356
- // src/lib/runtimeLineage.ts
4357
- var import_node_child_process = require("child_process");
4358
- var import_node_crypto2 = require("crypto");
4359
- var import_promises2 = __toESM(require("fs/promises"));
4360
- var import_node_path2 = __toESM(require("path"));
4361
- var import_types2 = require("@tradejs/types");
4362
- var import_strategies3 = require("@tradejs/node/strategies");
4363
- var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
4364
-
4365
- // src/lib/strategyGateFingerprint.ts
4366
- var import_node_crypto = require("crypto");
4367
- var import_node_module = require("module");
4368
4351
  var import_promises = __toESM(require("fs/promises"));
4369
4352
  var import_node_path = __toESM(require("path"));
4370
- var toPackageToken = (strategyName) => strategyName.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
4371
- var getStrategyPackageName = (strategyName) => strategyName === "TrendLine" || strategyName === "ReverseTrendLine" ? "@tradejs/strategy-trend-line" : `@tradejs/strategy-${toPackageToken(strategyName)}`;
4372
- var gateFingerprintSpecifiers = (strategyName) => [
4373
- getStrategyPackageName(strategyName),
4374
- "@tradejs/strategy-kit/ai-gate",
4375
- "@tradejs/node/ai",
4376
- "@tradejs/core/strategies"
4377
- ];
4378
- var resolveStrategyGateFingerprint = async ({
4379
- projectRoot,
4380
- strategyName,
4381
- gitSha
4382
- }) => {
4383
- const projectManifestPath = import_node_path.default.join(projectRoot, "package.json");
4384
- const projectExists = await import_promises.default.access(projectManifestPath).then(() => true).catch(() => false);
4385
- const requireFromProject = projectExists ? (0, import_node_module.createRequire)(projectManifestPath) : null;
4386
- const candidates = requireFromProject ? await Promise.all(
4387
- gateFingerprintSpecifiers(strategyName).map(async (specifier) => {
4388
- let resolvedPath;
4389
- try {
4390
- resolvedPath = requireFromProject.resolve(specifier);
4391
- } catch {
4392
- return null;
4393
- }
4394
- const content = await import_promises.default.readFile(resolvedPath).catch(() => null);
4395
- return content == null ? null : { specifier, content };
4396
- })
4397
- ) : [];
4398
- const sourceEntries = candidates.filter(
4399
- (entry) => entry != null
4400
- );
4401
- const hash = (0, import_node_crypto.createHash)("sha256");
4402
- hash.update(strategyName);
4403
- if (sourceEntries.length) {
4404
- for (const entry of sourceEntries) {
4405
- hash.update(entry.specifier);
4406
- hash.update(entry.content);
4407
- }
4408
- } else {
4409
- hash.update(gitSha ?? "unknown-git-sha");
4410
- }
4411
- return {
4412
- gateFingerprint: hash.digest("hex").slice(0, 16),
4413
- gateFingerprintFiles: sourceEntries.map((entry) => entry.specifier)
4414
- };
4415
- };
4416
4353
 
4417
4354
  // src/lib/runtimeLineage.ts
4418
- var RUNTIME_CONTEXT_ENV_KEYS = [
4419
- "AI_MODE",
4420
- "MIN_AI_QUALITY",
4421
- "INTERVAL",
4422
- "DERIVATIVES_CONTEXT_ENABLED",
4423
- "DERIVATIVES_CONTEXT_TARGET_ENABLED",
4424
- "DERIVATIVES_CONTEXT_LOOKBACK_HOURS",
4425
- "DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS",
4426
- "DERIVATIVES_CONTEXT_EXCHANGE_PRIORITY",
4427
- "COINMARKETCAP_CONTEXT_ENABLED",
4428
- "COINMARKETCAP_CONTEXT_MAX_AGE_MS",
4429
- "COINMARKETCAP_CONTEXT_EXCHANGE_LIQUIDITY_ENABLED",
4430
- "COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS",
4431
- "COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED",
4432
- "COINMARKETCAP_CONTEXT_HISTORICAL_ACCESS_MONTHS",
4433
- "COINMARKETCAP_CONTEXT_BACKFILL_ENABLED",
4434
- "COINMARKETCAP_CONTEXT_BACKFILL_MAX_DAYS",
4435
- "COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS",
4436
- "HYPERLIQUID_WHALE_CONTEXT_ENABLED",
4437
- "HYPERLIQUID_WHALE_BACKFILL_ENABLED",
4438
- "HYPERLIQUID_WHALE_MIN_COVERAGE_PCT",
4439
- "HYPERLIQUID_WHALE_CONCURRENCY",
4440
- "HYPERLIQUID_WHALE_RATE_LIMIT_WEIGHT"
4441
- ];
4442
- var RUNTIME_CONTEXT_DATA_MODEL = {
4443
- derivativesSourceIntervals: "15m",
4444
- derivativesDerivedIntervals: "1h",
4445
- derivativesHourlyFallback: "stored-1h",
4446
- derivativesDataModelVersion: 2,
4447
- coinMarketCapAvailabilityModel: "closed-interval-all-daily",
4448
- coinMarketCapDataModelVersion: 3,
4449
- binanceBreadthUniverseModel: "fixed-versioned-top5-top10-top30-top50-top100",
4450
- binanceBreadthDataModelVersion: 2,
4451
- hyperliquidWhaleCanonicalInterval: "1m",
4452
- hyperliquidWhaleGateMinNotionalUsd: 5e4,
4453
- hyperliquidWhaleMinCoveragePct: 0.8,
4454
- hyperliquidWhaleDataModelVersion: import_types2.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
4455
- };
4355
+ var import_node_crypto = require("crypto");
4456
4356
  var normalizeForStableJson = (value) => {
4457
4357
  if (Array.isArray(value)) {
4458
4358
  return value.map(normalizeForStableJson);
@@ -4477,210 +4377,48 @@ var resolveRuntimeMaxLossValue = (config) => {
4477
4377
  );
4478
4378
  return Number.isFinite(maxLossValue) ? maxLossValue : null;
4479
4379
  };
4480
- var fingerprintRuntimeValue = (value) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
4481
- var gitLineageCache = /* @__PURE__ */ new Map();
4482
- var gateFingerprintCache = /* @__PURE__ */ new Map();
4483
- var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
4484
- var snapshotFingerprintCache = /* @__PURE__ */ new Map();
4485
- var resolveGitLineage = (projectRoot) => {
4486
- const cached = gitLineageCache.get(projectRoot);
4487
- if (cached) {
4488
- return cached;
4489
- }
4490
- const envSha = String(process.env.TRADEJS_GIT_SHA ?? "").trim();
4491
- if (envSha && envSha !== "unknown") {
4492
- const result = {
4493
- gitSha: envSha,
4494
- gitDirty: false
4495
- };
4496
- gitLineageCache.set(projectRoot, result);
4497
- return result;
4498
- }
4499
- try {
4500
- const gitSha = (0, import_node_child_process.execFileSync)("git", ["rev-parse", "HEAD"], {
4501
- cwd: projectRoot,
4502
- encoding: "utf8",
4503
- stdio: ["ignore", "pipe", "ignore"]
4504
- }).trim();
4505
- const status = (0, import_node_child_process.execFileSync)(
4506
- "git",
4507
- ["status", "--porcelain", "--untracked-files=normal"],
4508
- {
4509
- cwd: projectRoot,
4510
- encoding: "utf8",
4511
- stdio: ["ignore", "pipe", "ignore"]
4512
- }
4513
- ).trim();
4514
- const result = {
4515
- gitSha: gitSha || null,
4516
- gitDirty: status.length > 0
4517
- };
4518
- gitLineageCache.set(projectRoot, result);
4519
- return result;
4520
- } catch {
4521
- const result = {
4522
- gitSha: null,
4523
- gitDirty: null
4524
- };
4525
- gitLineageCache.set(projectRoot, result);
4526
- return result;
4527
- }
4528
- };
4529
- var readOptionalFile = async (filePath) => {
4530
- try {
4531
- return await import_promises2.default.readFile(filePath);
4532
- } catch {
4533
- return null;
4534
- }
4535
- };
4536
- var resolveBinanceBreadthFingerprint = async (projectRoot) => {
4537
- if (binanceBreadthFingerprintCache.has(projectRoot)) {
4538
- return binanceBreadthFingerprintCache.get(projectRoot) ?? null;
4539
- }
4540
- const content = await readOptionalFile(
4541
- import_node_path2.default.join(
4542
- projectRoot,
4543
- "packages/node/src/config/binanceBreadthUniverses.json"
4544
- )
4545
- );
4546
- let result = null;
4547
- if (content) {
4548
- try {
4549
- const parsed = JSON.parse(content.toString("utf8"));
4550
- result = typeof parsed.fingerprint === "string" && parsed.fingerprint.trim() ? parsed.fingerprint.trim() : null;
4551
- } catch {
4552
- result = null;
4553
- }
4554
- }
4555
- binanceBreadthFingerprintCache.set(projectRoot, result);
4556
- return result;
4557
- };
4558
- var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
4559
- const cacheKey = `${projectRoot}:${relativePath}`;
4560
- if (snapshotFingerprintCache.has(cacheKey)) {
4561
- return snapshotFingerprintCache.get(cacheKey) ?? null;
4562
- }
4563
- const content = await readOptionalFile(import_node_path2.default.join(projectRoot, relativePath));
4564
- let result = null;
4565
- if (content) {
4566
- try {
4567
- const parsed = JSON.parse(content.toString("utf8"));
4568
- result = typeof parsed.fingerprint === "string" && parsed.fingerprint.trim() ? parsed.fingerprint.trim() : null;
4569
- } catch {
4570
- result = null;
4571
- }
4572
- }
4573
- snapshotFingerprintCache.set(cacheKey, result);
4574
- return result;
4575
- };
4576
- var resolveRuntimeGateFingerprint = async ({
4577
- projectRoot,
4578
- strategyName,
4579
- gitSha
4580
- }) => {
4581
- const cacheKey = `${projectRoot}:${strategyName}:${gitSha ?? "unknown"}`;
4582
- const cached = gateFingerprintCache.get(cacheKey);
4583
- if (cached) return cached;
4584
- const result = await resolveStrategyGateFingerprint({
4585
- projectRoot,
4586
- strategyName,
4587
- gitSha
4588
- });
4589
- gateFingerprintCache.set(cacheKey, result);
4590
- return result;
4591
- };
4592
- async function buildRuntimeLineage({
4593
- projectRoot,
4594
- strategyName,
4595
- compositionId,
4380
+ var fingerprintRuntimeValue = (value) => (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
4381
+ var buildRuntimeLineage = async ({
4596
4382
  strategyRevision,
4597
4383
  deploymentCompositionId,
4598
4384
  strategyPackageVersion,
4599
4385
  strategyDependencyVersions,
4600
4386
  runtimePackageVersion,
4601
- config,
4602
- runContext,
4603
- env = process.env
4604
- }) {
4605
- if (strategyRevision != null) {
4606
- if (!/^sr1:[a-f0-9]{16}$/.test(strategyRevision)) {
4607
- throw new Error(`Invalid strategy revision: ${strategyRevision}`);
4608
- }
4609
- if (typeof deploymentCompositionId !== "string" || !/^dc1:[a-f0-9]{16}$/.test(deploymentCompositionId)) {
4610
- throw new Error(
4611
- `Invalid deployment composition id: ${String(deploymentCompositionId)}`
4612
- );
4613
- }
4614
- if (typeof strategyPackageVersion !== "string" || !strategyPackageVersion.trim() || typeof runtimePackageVersion !== "string" || !runtimePackageVersion.trim()) {
4615
- throw new Error("Invalid revision package versions");
4616
- }
4617
- if (!strategyDependencyVersions || Object.keys(strategyDependencyVersions).length === 0 || Object.entries(strategyDependencyVersions).some(
4618
- ([name, version]) => !name.startsWith("@tradejs/") || typeof version !== "string" || !version.trim()
4619
- )) {
4620
- throw new Error("Invalid strategy dependency versions");
4621
- }
4622
- return {
4623
- schemaVersion: 3,
4624
- strategyRevision,
4625
- deploymentCompositionId,
4626
- strategyPackageVersion,
4627
- strategyDependencyVersions,
4628
- runtimePackageVersion,
4629
- maxLossValue: resolveRuntimeMaxLossValue(config)
4630
- };
4387
+ config
4388
+ }) => {
4389
+ if (!/^sr1:[a-f0-9]{16}$/.test(strategyRevision)) {
4390
+ throw new Error(`Invalid strategy revision: ${strategyRevision}`);
4391
+ }
4392
+ if (!/^dc1:[a-f0-9]{16}$/.test(deploymentCompositionId)) {
4393
+ throw new Error(
4394
+ `Invalid deployment composition id: ${deploymentCompositionId}`
4395
+ );
4396
+ }
4397
+ if (!strategyPackageVersion.trim() || !runtimePackageVersion.trim()) {
4398
+ throw new Error("Invalid revision package versions");
4399
+ }
4400
+ if (Object.keys(strategyDependencyVersions).length === 0 || Object.entries(strategyDependencyVersions).some(
4401
+ ([name, version]) => !name.startsWith("@tradejs/") || typeof version !== "string" || !version.trim()
4402
+ )) {
4403
+ throw new Error("Invalid strategy dependency versions");
4631
4404
  }
4632
- const git = resolveGitLineage(projectRoot);
4633
- const hyperliquidPerpUniverseFingerprint = typeof import_strategies3.getHyperliquidPerpUniverseSnapshot === "function" ? (0, import_strategies3.getHyperliquidPerpUniverseSnapshot)().fingerprint : await resolveSnapshotFingerprint(
4634
- projectRoot,
4635
- "packages/node/src/config/hyperliquidPerpUniverse.json"
4636
- );
4637
- const hyperliquidWhaleRegistryFingerprint = typeof import_strategies3.getHyperliquidWhaleRegistrySnapshot === "function" ? (0, import_strategies3.getHyperliquidWhaleRegistrySnapshot)().fingerprint : await resolveSnapshotFingerprint(
4638
- projectRoot,
4639
- "packages/node/src/config/hyperliquidWhales.json"
4640
- );
4641
- const context = {
4642
- ...Object.fromEntries(
4643
- RUNTIME_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
4644
- ),
4645
- ...RUNTIME_CONTEXT_DATA_MODEL,
4646
- binanceBreadthUniverseFingerprint: await resolveBinanceBreadthFingerprint(projectRoot),
4647
- hyperliquidPerpUniverseFingerprint,
4648
- hyperliquidWhaleRegistryFingerprint,
4649
- ...runContext
4650
- };
4651
4405
  return {
4652
- schemaVersion: 1,
4653
- compositionId: compositionId?.trim() || null,
4654
- ...git,
4655
- gateFingerprint: (await resolveRuntimeGateFingerprint({
4656
- projectRoot,
4657
- strategyName,
4658
- gitSha: git.gitSha
4659
- })).gateFingerprint,
4660
- // Position risk is tracked separately so changing trade size does not
4661
- // masquerade as a change to the core + gate decision logic.
4662
- configFingerprint: (0, import_strategyReleaseEvidence.strategyLogicConfigFingerprint)(config),
4663
- contextFingerprint: fingerprintRuntimeValue(context),
4406
+ schemaVersion: 3,
4407
+ strategyRevision,
4408
+ deploymentCompositionId,
4409
+ strategyPackageVersion,
4410
+ strategyDependencyVersions,
4411
+ runtimePackageVersion,
4664
4412
  maxLossValue: resolveRuntimeMaxLossValue(config)
4665
4413
  };
4666
- }
4667
- var runtimeLineageKey = (lineage) => lineage.schemaVersion === 3 ? [
4414
+ };
4415
+ var runtimeLineageKey = (lineage) => [
4668
4416
  "v3",
4669
4417
  lineage.deploymentCompositionId,
4670
4418
  lineage.strategyRevision,
4671
4419
  lineage.strategyPackageVersion,
4672
- `deps:${fingerprintRuntimeValue(
4673
- lineage.strategyDependencyVersions
4674
- ).slice(0, 16)}`,
4420
+ `deps:${fingerprintRuntimeValue(lineage.strategyDependencyVersions).slice(0, 16)}`,
4675
4421
  lineage.runtimePackageVersion
4676
- ].join(":") : [
4677
- lineage.schemaVersion,
4678
- lineage.compositionId ?? "unbound-composition",
4679
- lineage.gitSha ?? "unknown",
4680
- lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
4681
- lineage.gateFingerprint,
4682
- lineage.configFingerprint,
4683
- lineage.contextFingerprint
4684
4422
  ].join(":");
4685
4423
  var runtimeLineagesMatch = (left, right) => left != null && right != null && runtimeLineageKey(left) === runtimeLineageKey(right);
4686
4424
  var runtimeLineagesComparable = (left, right) => runtimeLineagesMatch(left, right) && typeof left?.maxLossValue === "number" && Number.isFinite(left.maxLossValue) && typeof right?.maxLossValue === "number" && Number.isFinite(right.maxLossValue) && left.maxLossValue === right.maxLossValue;
@@ -4719,7 +4457,7 @@ var samePackageVersions = (left, right) => {
4719
4457
  );
4720
4458
  return JSON.stringify(leftEntries) === JSON.stringify(rightEntries);
4721
4459
  };
4722
- var lineageMatchesStrategySnapshot = ({
4460
+ var runtimeLineageMatchesStrategySnapshot = ({
4723
4461
  lineage,
4724
4462
  deployment,
4725
4463
  strategy
@@ -4766,6 +4504,82 @@ var parseRuntimeEvidenceDeploymentSnapshot = (value) => {
4766
4504
  };
4767
4505
  };
4768
4506
  var activeRuntimeEvidenceStrategies = (deployment) => deployment.enabled ? deployment.strategies.filter((strategy) => strategy.enabled) : [];
4507
+ var unwrapRuntimeEvidenceRows = (value, field) => (Array.isArray(value) ? value : []).map((candidate) => {
4508
+ const record = isRecord(candidate) ? candidate : null;
4509
+ const nested = record && isRecord(record[field]) ? record[field] : null;
4510
+ return nested ?? record;
4511
+ }).filter((record) => record != null);
4512
+ var assertCurrentRuntimeEvidenceArtifact = (artifact) => {
4513
+ if (!isRecord(artifact) || artifact.reportType !== "runtime-evidence") {
4514
+ throw new Error("Invalid runtime evidence artifact");
4515
+ }
4516
+ const deployment = parseRuntimeEvidenceDeploymentSnapshot(
4517
+ artifact.deployment
4518
+ );
4519
+ const activeStrategies = activeRuntimeEvidenceStrategies(deployment);
4520
+ if (activeStrategies.length === 0) {
4521
+ throw new Error("Runtime evidence deployment has no enabled strategies");
4522
+ }
4523
+ const runtime = isRecord(artifact.runtime) ? artifact.runtime : null;
4524
+ if (!runtime) {
4525
+ throw new Error("Runtime evidence payload is missing");
4526
+ }
4527
+ if ("strategyConfigs" in runtime) {
4528
+ throw new Error(
4529
+ "Runtime evidence payload contains removed strategyConfigs data"
4530
+ );
4531
+ }
4532
+ const rowFields = [
4533
+ "trades",
4534
+ "signals",
4535
+ "evaluations",
4536
+ "lineageScopes"
4537
+ ];
4538
+ for (const field of rowFields) {
4539
+ if (!Array.isArray(runtime[field])) {
4540
+ throw new Error(`Runtime evidence payload ${field} must be an array`);
4541
+ }
4542
+ }
4543
+ const snapshots = new Map(
4544
+ activeStrategies.map((strategy) => [strategy.strategyName, strategy])
4545
+ );
4546
+ const assertRows = (label, rows, lineageField) => {
4547
+ for (const row of rows) {
4548
+ const strategyName = String(row.strategy ?? "").trim();
4549
+ const strategy = snapshots.get(strategyName);
4550
+ if (!strategy || row.deploymentId !== deployment.id || row.accountId !== deployment.accountId || !runtimeLineageMatchesStrategySnapshot({
4551
+ lineage: isRecord(row[lineageField]) ? row[lineageField] : void 0,
4552
+ deployment,
4553
+ strategy
4554
+ })) {
4555
+ throw new Error(
4556
+ `Runtime evidence ${label} row is outside the current embedded deployment lineage`
4557
+ );
4558
+ }
4559
+ }
4560
+ };
4561
+ assertRows(
4562
+ "trade",
4563
+ unwrapRuntimeEvidenceRows(runtime.trades, "trade"),
4564
+ "runtimeLineage"
4565
+ );
4566
+ assertRows(
4567
+ "signal",
4568
+ unwrapRuntimeEvidenceRows(runtime.signals, "signal"),
4569
+ "runtimeLineage"
4570
+ );
4571
+ assertRows(
4572
+ "evaluation",
4573
+ unwrapRuntimeEvidenceRows(runtime.evaluations, "evaluation"),
4574
+ "runtimeLineage"
4575
+ );
4576
+ assertRows(
4577
+ "lineage scope",
4578
+ unwrapRuntimeEvidenceRows(runtime.lineageScopes, "lineageScope"),
4579
+ "lineage"
4580
+ );
4581
+ return deployment;
4582
+ };
4769
4583
  var resolveRuntimeEvidenceTickerUniverse = ({
4770
4584
  deployment,
4771
4585
  lineageScopes
@@ -4798,7 +4612,7 @@ var resolveRuntimeEvidenceTickerUniverse = ({
4798
4612
  const tickers = /* @__PURE__ */ new Set();
4799
4613
  for (const scope of lineageScopes) {
4800
4614
  const strategy = strategiesByName.get(scope.strategy);
4801
- if (!strategy || scope.deploymentId !== deployment.id || scope.accountId !== deployment.accountId || !isNonEmptyString(scope.symbol) || !lineageMatchesStrategySnapshot({
4615
+ if (!strategy || scope.deploymentId !== deployment.id || scope.accountId !== deployment.accountId || !isNonEmptyString(scope.symbol) || !runtimeLineageMatchesStrategySnapshot({
4802
4616
  lineage: scope.lineage,
4803
4617
  deployment,
4804
4618
  strategy
@@ -4864,14 +4678,15 @@ var readRuntimeEvidenceArtifact = async ({
4864
4678
  filePath,
4865
4679
  projectRoot
4866
4680
  }) => {
4867
- const resolvedPath = import_node_path3.default.resolve(projectRoot, filePath);
4681
+ const resolvedPath = import_node_path.default.resolve(projectRoot, filePath);
4868
4682
  const root = asRecord(
4869
- JSON.parse(await import_promises3.default.readFile(resolvedPath, "utf8"))
4683
+ JSON.parse(await import_promises.default.readFile(resolvedPath, "utf8"))
4870
4684
  );
4871
4685
  const runtime = asRecord(root?.runtime) ?? root;
4872
4686
  if (!root || !runtime) {
4873
4687
  throw new Error(`Invalid runtime evidence JSON: ${resolvedPath}`);
4874
4688
  }
4689
+ assertCurrentRuntimeEvidenceArtifact(root);
4875
4690
  const rootWindow = asRecord(root.window);
4876
4691
  const runtimeWindow = asRecord(runtime.window);
4877
4692
  return {
@@ -5132,13 +4947,13 @@ var compactReplaySignal = (signal) => {
5132
4947
  var import_chalk9 = __toESM(require("chalk"));
5133
4948
  var import_progress4 = __toESM(require("progress"));
5134
4949
  var import_backtest = require("@tradejs/core/backtest");
5135
- var import_strategies4 = require("@tradejs/core/strategies");
4950
+ var import_strategies3 = require("@tradejs/core/strategies");
5136
4951
  var import_time3 = require("@tradejs/core/time");
5137
4952
  var import_logger = require("@tradejs/infra/logger");
5138
4953
  var import_redis3 = require("@tradejs/infra/redis");
5139
4954
  var import_connectors6 = require("@tradejs/node/connectors");
5140
4955
  var import_cli4 = require("@tradejs/node/cli");
5141
- var import_strategies5 = require("@tradejs/node/strategies");
4956
+ var import_strategies4 = require("@tradejs/node/strategies");
5142
4957
  var import_runtimeStrategies3 = require("@tradejs/node/runtimeStrategies");
5143
4958
 
5144
4959
  // src/lib/replay/portfolioReplayConnector.ts
@@ -5604,7 +5419,7 @@ var loadRuntimeStrategies = async (runtimeStrategies) => {
5604
5419
  strategyConfig,
5605
5420
  selection
5606
5421
  } = runtimeStrategy;
5607
- const strategyCreator = await (0, import_strategies5.getStrategyCreator)(
5422
+ const strategyCreator = await (0, import_strategies4.getStrategyCreator)(
5608
5423
  strategyName,
5609
5424
  replayProjectRoot
5610
5425
  );
@@ -5843,8 +5658,6 @@ var runHistoricalSignalsReplay = async ({
5843
5658
  strategyResults
5844
5659
  }) => {
5845
5660
  const runtimeLineage = await buildRuntimeLineage({
5846
- projectRoot: replayProjectRoot,
5847
- strategyName,
5848
5661
  strategyRevision,
5849
5662
  deploymentCompositionId,
5850
5663
  strategyPackageVersion,
@@ -5854,11 +5667,6 @@ var runHistoricalSignalsReplay = async ({
5854
5667
  configId: strategyRevision,
5855
5668
  strategyConfig,
5856
5669
  symbolResultConfig: strategyResults?.[symbol]?.config ?? null
5857
- },
5858
- runContext: {
5859
- connectorName: connectorName.toLowerCase(),
5860
- interval: String(interval),
5861
- universe: preparedRun.universe ?? null
5862
5670
  }
5863
5671
  });
5864
5672
  runtimeLineages.push({
@@ -5989,7 +5797,7 @@ var runHistoricalSignalsReplay = async ({
5989
5797
  );
5990
5798
  if (result && typeof result !== "string") {
5991
5799
  result.runtimeLineage = strategyRuntime.runtimeLineage;
5992
- await (0, import_strategies5.enrichSignalWithBinanceMarketContext)({
5800
+ await (0, import_strategies4.enrichSignalWithBinanceMarketContext)({
5993
5801
  signal: result,
5994
5802
  env: "PARITY"
5995
5803
  });
@@ -6013,8 +5821,8 @@ var runHistoricalSignalsReplay = async ({
6013
5821
  }
6014
5822
  } finally {
6015
5823
  for (const keyPrefix of sharedReplayKeyPrefixes) {
6016
- (0, import_strategies4.releaseStrategyIndicatorsReplayCache)(keyPrefix);
6017
- (0, import_strategies4.releaseStrategyReplayCache)(keyPrefix);
5824
+ (0, import_strategies3.releaseStrategyIndicatorsReplayCache)(keyPrefix);
5825
+ (0, import_strategies3.releaseStrategyReplayCache)(keyPrefix);
6018
5826
  }
6019
5827
  }
6020
5828
  const artifacts = replayConnector.getReplayArtifacts();
@@ -8671,8 +8479,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
8671
8479
  };
8672
8480
 
8673
8481
  // src/lib/replay/outputReport.ts
8674
- var import_promises4 = __toESM(require("fs/promises"));
8675
- var import_node_path4 = __toESM(require("path"));
8482
+ var import_promises2 = __toESM(require("fs/promises"));
8483
+ var import_node_path2 = __toESM(require("path"));
8676
8484
  var import_data = require("@tradejs/core/data");
8677
8485
  var formatMsk = (timestamp) => typeof timestamp === "number" && Number.isFinite(timestamp) ? new Intl.DateTimeFormat("ru-RU", {
8678
8486
  timeZone: "Europe/Moscow",
@@ -8911,33 +8719,20 @@ var renderLineageRows = (runtimeComparison) => {
8911
8719
  const existing = grouped.get(record.strategy) ?? {
8912
8720
  strategy: record.strategy,
8913
8721
  scopes: 0,
8914
- gitShas: /* @__PURE__ */ new Set(),
8915
- dirty: /* @__PURE__ */ new Set(),
8916
- gates: /* @__PURE__ */ new Set(),
8917
- configs: /* @__PURE__ */ new Set(),
8918
- contexts: /* @__PURE__ */ new Set()
8722
+ deploymentCompositions: /* @__PURE__ */ new Set(),
8723
+ strategyRevisions: /* @__PURE__ */ new Set(),
8724
+ strategyPackages: /* @__PURE__ */ new Set(),
8725
+ dependencySets: /* @__PURE__ */ new Set(),
8726
+ runtimePackages: /* @__PURE__ */ new Set()
8919
8727
  };
8920
8728
  existing.scopes += 1;
8921
- if (record.lineage.schemaVersion === 3) {
8922
- existing.gitShas.add(record.lineage.deploymentCompositionId);
8923
- existing.dirty.add("n/a");
8924
- existing.gates.add("package-owned");
8925
- existing.configs.add(record.lineage.strategyRevision);
8926
- existing.contexts.add(
8927
- [
8928
- record.lineage.strategyPackageVersion,
8929
- record.lineage.runtimePackageVersion
8930
- ].join("/")
8931
- );
8932
- } else {
8933
- existing.gitShas.add(record.lineage.gitSha ?? "unknown");
8934
- existing.dirty.add(
8935
- record.lineage.gitDirty == null ? "unknown" : String(record.lineage.gitDirty)
8936
- );
8937
- existing.gates.add(record.lineage.gateFingerprint);
8938
- existing.configs.add(record.lineage.configFingerprint);
8939
- existing.contexts.add(record.lineage.contextFingerprint);
8940
- }
8729
+ existing.deploymentCompositions.add(record.lineage.deploymentCompositionId);
8730
+ existing.strategyRevisions.add(record.lineage.strategyRevision);
8731
+ existing.strategyPackages.add(record.lineage.strategyPackageVersion);
8732
+ existing.dependencySets.add(
8733
+ Object.entries(record.lineage.strategyDependencyVersions).sort(([left], [right]) => left.localeCompare(right)).map(([name, version]) => `${name}@${version}`).join(", ")
8734
+ );
8735
+ existing.runtimePackages.add(record.lineage.runtimePackageVersion);
8941
8736
  grouped.set(record.strategy, existing);
8942
8737
  }
8943
8738
  const formatSet = (values) => {
@@ -8948,29 +8743,29 @@ var renderLineageRows = (runtimeComparison) => {
8948
8743
  [
8949
8744
  "Strategy",
8950
8745
  "Scopes",
8951
- "Git SHA",
8952
- "Dirty",
8953
- "Gate",
8954
- "Config variants",
8955
- "Context"
8746
+ "Deployment composition",
8747
+ "Strategy revision",
8748
+ "Strategy package",
8749
+ "Dependencies",
8750
+ "Runtime package"
8956
8751
  ],
8957
8752
  [...grouped.values()].sort((left, right) => left.strategy.localeCompare(right.strategy)).map(
8958
8753
  ({
8959
8754
  strategy,
8960
8755
  scopes,
8961
- gitShas,
8962
- dirty,
8963
- gates,
8964
- configs,
8965
- contexts
8756
+ deploymentCompositions,
8757
+ strategyRevisions,
8758
+ strategyPackages,
8759
+ dependencySets,
8760
+ runtimePackages
8966
8761
  }) => [
8967
8762
  strategy,
8968
8763
  scopes,
8969
- formatSet(gitShas),
8970
- formatSet(dirty),
8971
- formatSet(gates),
8972
- `${configs.size}: ${formatSet(configs)}`,
8973
- formatSet(contexts)
8764
+ formatSet(deploymentCompositions),
8765
+ formatSet(strategyRevisions),
8766
+ formatSet(strategyPackages),
8767
+ formatSet(dependencySets),
8768
+ formatSet(runtimePackages)
8974
8769
  ]
8975
8770
  )
8976
8771
  ) : "_No replay lineage records._";
@@ -8989,8 +8784,8 @@ var writeReplayOutputReport = async ({
8989
8784
  strategySnapshot,
8990
8785
  runtimeComparison
8991
8786
  }) => {
8992
- const outputDir = import_node_path4.default.resolve(projectRoot, "data/replay/output");
8993
- await import_promises4.default.mkdir(outputDir, { recursive: true });
8787
+ const outputDir = import_node_path2.default.resolve(projectRoot, "data/replay/output");
8788
+ await import_promises2.default.mkdir(outputDir, { recursive: true });
8994
8789
  const perTradeAnalysis = buildPerTradeAnalysis(runtimeComparison);
8995
8790
  const report = {
8996
8791
  reportType: "replay-output",
@@ -9025,8 +8820,8 @@ var writeReplayOutputReport = async ({
9025
8820
  runtimeComparison,
9026
8821
  perTradeAnalysis
9027
8822
  };
9028
- const markdownPath = import_node_path4.default.join(outputDir, `${timestamp}-replay-report.md`);
9029
- const jsonPath = import_node_path4.default.join(outputDir, `${timestamp}-replay-report.json`);
8823
+ const markdownPath = import_node_path2.default.join(outputDir, `${timestamp}-replay-report.md`);
8824
+ const jsonPath = import_node_path2.default.join(outputDir, `${timestamp}-replay-report.json`);
9030
8825
  const lines = [
9031
8826
  "# Replay Report",
9032
8827
  "",
@@ -9092,8 +8887,8 @@ var writeReplayOutputReport = async ({
9092
8887
  ""
9093
8888
  ];
9094
8889
  await Promise.all([
9095
- import_promises4.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
9096
- import_promises4.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
8890
+ import_promises2.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
8891
+ import_promises2.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
9097
8892
  `, "utf8")
9098
8893
  ]);
9099
8894
  return {