@tradejs/cli 3.1.16 → 3.1.17

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