@tradejs/cli 3.1.27-beta.247 → 3.1.27-beta.249

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
@@ -26625,6 +26625,148 @@ var init_runtimeFeedbackSync2 = __esm({
26625
26625
  }
26626
26626
  });
26627
26627
 
26628
+ // src/lib/runtimeEvidenceCompositions.ts
26629
+ var stableSnapshotIdentity, producerIdentity, loadRuntimeEvidenceCompositionSnapshots, rowCompositionId, rowTimestamp, belongsToSnapshot, splitRuntimeEvidenceByComposition;
26630
+ var init_runtimeEvidenceCompositions = __esm({
26631
+ "src/lib/runtimeEvidenceCompositions.ts"() {
26632
+ "use strict";
26633
+ init_runtimeEvidenceArtifacts();
26634
+ init_runtimeEvidenceDeployment();
26635
+ init_runtimeEvidenceProducer();
26636
+ stableSnapshotIdentity = (deployment) => JSON.stringify({ ...deployment, tickers: void 0 });
26637
+ producerIdentity = (producer) => JSON.stringify(producer);
26638
+ loadRuntimeEvidenceCompositionSnapshots = async ({
26639
+ publishRoot,
26640
+ currentDeployment,
26641
+ currentProducer
26642
+ }) => {
26643
+ const snapshots = /* @__PURE__ */ new Map();
26644
+ const register = (snapshot) => {
26645
+ const compositionId = snapshot.deployment.deploymentCompositionId;
26646
+ const known = snapshots.get(compositionId);
26647
+ if (known && (stableSnapshotIdentity(known.deployment) !== stableSnapshotIdentity(snapshot.deployment) || producerIdentity(known.producer) !== producerIdentity(snapshot.producer))) {
26648
+ throw new Error(
26649
+ `Conflicting runtime evidence snapshot for composition ${compositionId}`
26650
+ );
26651
+ }
26652
+ snapshots.set(compositionId, snapshot);
26653
+ };
26654
+ const bundleDirs = await discoverRuntimeEvidenceBundles(publishRoot);
26655
+ for (const bundleDir of bundleDirs) {
26656
+ const bundle = await verifyRuntimeEvidenceBundle(bundleDir);
26657
+ const deployment = parseRuntimeEvidenceDeploymentSnapshot(
26658
+ bundle.artifact.deployment
26659
+ );
26660
+ if (deployment.id !== currentDeployment.id) continue;
26661
+ if (bundle.artifact.producer == null) continue;
26662
+ register({
26663
+ deployment: { ...deployment, tickers: void 0 },
26664
+ producer: parseRuntimeEvidenceProducer(bundle.artifact.producer)
26665
+ });
26666
+ }
26667
+ const historicalCurrent = snapshots.get(
26668
+ currentDeployment.deploymentCompositionId
26669
+ );
26670
+ if (historicalCurrent && stableSnapshotIdentity(historicalCurrent.deployment) !== stableSnapshotIdentity(currentDeployment)) {
26671
+ throw new Error(
26672
+ `Current runtime deployment conflicts with stored composition ${currentDeployment.deploymentCompositionId}`
26673
+ );
26674
+ }
26675
+ snapshots.set(currentDeployment.deploymentCompositionId, {
26676
+ deployment: { ...currentDeployment, tickers: void 0 },
26677
+ producer: currentProducer
26678
+ });
26679
+ return snapshots;
26680
+ };
26681
+ rowCompositionId = (row) => row.runtimeLineage?.deploymentCompositionId ?? row.lineage?.deploymentCompositionId ?? null;
26682
+ rowTimestamp = (row) => {
26683
+ for (const field of [
26684
+ "firstTimestamp",
26685
+ "entryTimestamp",
26686
+ "timestamp",
26687
+ "lastTimestamp"
26688
+ ]) {
26689
+ const value = row[field];
26690
+ if (typeof value === "number" && Number.isFinite(value)) return value;
26691
+ }
26692
+ return Number.POSITIVE_INFINITY;
26693
+ };
26694
+ belongsToSnapshot = (row, deployment) => {
26695
+ const strategy = activeRuntimeEvidenceStrategies(deployment).find(
26696
+ (candidate) => candidate.strategyName === row.strategy
26697
+ );
26698
+ return Boolean(
26699
+ strategy && row.deploymentId === deployment.id && row.accountId === deployment.accountId && runtimeLineageMatchesStrategySnapshot({
26700
+ lineage: row.runtimeLineage ?? row.lineage,
26701
+ deployment,
26702
+ strategy
26703
+ })
26704
+ );
26705
+ };
26706
+ splitRuntimeEvidenceByComposition = ({
26707
+ evidence,
26708
+ snapshots,
26709
+ includeEmptyCompositionIds = [],
26710
+ ignoreUnknownCompositions = false
26711
+ }) => {
26712
+ const observed = /* @__PURE__ */ new Set();
26713
+ for (const row of [
26714
+ ...evidence.trades,
26715
+ ...evidence.signals,
26716
+ ...evidence.evaluations,
26717
+ ...evidence.lineageScopes
26718
+ ]) {
26719
+ const compositionId = rowCompositionId(row);
26720
+ if (compositionId) observed.add(compositionId);
26721
+ }
26722
+ for (const compositionId of includeEmptyCompositionIds) {
26723
+ observed.add(compositionId);
26724
+ }
26725
+ const missing = [...observed].filter(
26726
+ (compositionId) => !snapshots.has(compositionId)
26727
+ );
26728
+ if (missing.length && !ignoreUnknownCompositions) {
26729
+ throw new Error(
26730
+ `Runtime evidence has no verified deployment snapshot for composition(s): ${missing.sort().join(", ")}`
26731
+ );
26732
+ }
26733
+ return [...observed].filter((compositionId) => snapshots.has(compositionId)).map((compositionId) => snapshots.get(compositionId)).map(({ deployment, producer }) => {
26734
+ const trades = evidence.trades.filter(
26735
+ (row) => belongsToSnapshot(row, deployment)
26736
+ );
26737
+ const signals2 = evidence.signals.filter(
26738
+ (row) => belongsToSnapshot(row, deployment)
26739
+ );
26740
+ const evaluations = evidence.evaluations.filter(
26741
+ (row) => belongsToSnapshot(row, deployment)
26742
+ );
26743
+ const lineageScopes = evidence.lineageScopes.filter(
26744
+ (row) => belongsToSnapshot(row, deployment)
26745
+ );
26746
+ const timestamps = [
26747
+ ...trades,
26748
+ ...signals2,
26749
+ ...evaluations,
26750
+ ...lineageScopes
26751
+ ].map((row) => rowTimestamp(row));
26752
+ return {
26753
+ deployment,
26754
+ producer,
26755
+ trades,
26756
+ signals: signals2,
26757
+ evaluations,
26758
+ lineageScopes,
26759
+ firstTimestamp: Math.min(...timestamps)
26760
+ };
26761
+ }).sort(
26762
+ (left, right) => left.firstTimestamp - right.firstTimestamp || left.deployment.deploymentCompositionId.localeCompare(
26763
+ right.deployment.deploymentCompositionId
26764
+ )
26765
+ );
26766
+ };
26767
+ }
26768
+ });
26769
+
26628
26770
  // src/lib/runtimeLineage.ts
26629
26771
  var import_node_crypto13, normalizeForStableJson2, resolveRuntimeMaxLossValue, fingerprintRuntimeValue, buildRuntimeLineage, runtimeLineageKey, runtimeLineagesMatch, runtimeLineagesComparable;
26630
26772
  var init_runtimeLineage = __esm({
@@ -26719,6 +26861,7 @@ var init_runtimeEvidence = __esm({
26719
26861
  import_chalk41 = __toESM(require("chalk"));
26720
26862
  init_runtimeDebugEvidence();
26721
26863
  init_runtimeEvidenceArtifacts();
26864
+ init_runtimeEvidenceCompositions();
26722
26865
  init_runtimeLineage();
26723
26866
  init_runtimeEvidenceProducer();
26724
26867
  init_runtimeEvidenceDeployment();
@@ -26811,15 +26954,30 @@ var init_runtimeEvidence = __esm({
26811
26954
  projectRoot: projectRoot11,
26812
26955
  deploymentId
26813
26956
  });
26814
- const activeStrategies = activeRuntimeEvidenceStrategies(
26815
- deploymentSnapshot
26816
- ).map(({ strategyName }) => strategyName);
26817
26957
  const requestedStrategies = parseStrategyFilter2(flags27.strategy);
26818
- const strategies = requestedStrategies.length ? requestedStrategies : activeStrategies;
26958
+ const evidence = await collectRuntimeDebugEvidence({
26959
+ userName: flags27.user,
26960
+ startTime,
26961
+ endTime,
26962
+ strategies: requestedStrategies.length ? requestedStrategies : void 0,
26963
+ deploymentId
26964
+ });
26965
+ const snapshots = publishDir && producer ? await loadRuntimeEvidenceCompositionSnapshots({
26966
+ publishRoot: import_node_path24.default.resolve(projectRoot11, publishDir),
26967
+ currentDeployment: deploymentSnapshot,
26968
+ currentProducer: producer
26969
+ }) : /* @__PURE__ */ new Map([
26970
+ [
26971
+ deploymentSnapshot.deploymentCompositionId,
26972
+ { deployment: deploymentSnapshot, producer }
26973
+ ]
26974
+ ]);
26819
26975
  const declaredStrategies = new Set(
26820
- deploymentSnapshot.strategies.map(({ strategyName }) => strategyName)
26976
+ [...snapshots.values()].flatMap(
26977
+ ({ deployment }) => deployment.strategies.map(({ strategyName }) => strategyName)
26978
+ )
26821
26979
  );
26822
- const unknownStrategies = strategies.filter(
26980
+ const unknownStrategies = requestedStrategies.filter(
26823
26981
  (strategyName) => !declaredStrategies.has(strategyName)
26824
26982
  );
26825
26983
  if (unknownStrategies.length) {
@@ -26827,97 +26985,94 @@ var init_runtimeEvidence = __esm({
26827
26985
  `Strategies are not declared in ${deploymentId}: ${unknownStrategies.join(", ")}`
26828
26986
  );
26829
26987
  }
26830
- const evidence = await collectRuntimeDebugEvidence({
26831
- userName: flags27.user,
26832
- startTime,
26833
- endTime,
26834
- strategies,
26835
- deploymentId
26988
+ const compositionRows = splitRuntimeEvidenceByComposition({
26989
+ evidence,
26990
+ snapshots,
26991
+ includeEmptyCompositionIds: publishDir ? [] : [deploymentSnapshot.deploymentCompositionId],
26992
+ ignoreUnknownCompositions: !publishDir
26836
26993
  });
26837
- const activeSnapshots = new Map(
26838
- activeRuntimeEvidenceStrategies(deploymentSnapshot).map((strategy) => [
26839
- strategy.strategyName,
26840
- strategy
26841
- ])
26842
- );
26843
- const belongsToCurrentDeployment = (row) => {
26844
- const snapshot = activeSnapshots.get(row.strategy);
26845
- return Boolean(
26846
- snapshot && row.deploymentId === deploymentSnapshot.id && row.accountId === deploymentSnapshot.accountId && runtimeLineageMatchesStrategySnapshot({
26847
- lineage: row.runtimeLineage,
26848
- deployment: deploymentSnapshot,
26849
- strategy: snapshot
26850
- })
26994
+ const artifacts = compositionRows.map((composition) => {
26995
+ const strategies = activeRuntimeEvidenceStrategies(composition.deployment).map(({ strategyName }) => strategyName).filter(
26996
+ (strategyName) => !requestedStrategies.length || requestedStrategies.includes(strategyName)
26851
26997
  );
26852
- };
26853
- const currentSignals = evidence.signals.filter(belongsToCurrentDeployment);
26854
- const currentEvaluations = evidence.evaluations.filter(
26855
- belongsToCurrentDeployment
26856
- );
26857
- const currentTrades = evidence.trades.filter(belongsToCurrentDeployment);
26858
- const currentLineageScopes = evidence.lineageScopes.filter(
26859
- (scope) => belongsToCurrentDeployment({
26860
- ...scope,
26861
- runtimeLineage: scope.lineage
26862
- })
26863
- );
26864
- const runtime = buildRuntimeEvidenceReportPayload({
26865
- userName: flags27.user,
26866
- startTime,
26867
- endTime,
26868
- signals: currentSignals,
26869
- evaluations: currentEvaluations,
26870
- trades: currentTrades,
26871
- lineageScopes: currentLineageScopes
26872
- });
26873
- const deployment = resolveRuntimeEvidenceTickerUniverse({
26874
- deployment: deploymentSnapshot,
26875
- lineageScopes: currentLineageScopes
26876
- });
26877
- const artifact = {
26878
- reportType: "runtime-evidence",
26879
- generatedAt: Date.now(),
26880
- ...producer ? { producer } : {},
26881
- userName: flags27.user,
26882
- window: {
26998
+ const runtime = buildRuntimeEvidenceReportPayload({
26999
+ userName: flags27.user,
26883
27000
  startTime,
26884
27001
  endTime,
26885
- startIso: new Date(startTime).toISOString(),
26886
- endIso: new Date(endTime).toISOString(),
26887
- source
26888
- },
26889
- strategies,
26890
- deployment,
26891
- runtime
26892
- };
27002
+ signals: composition.signals,
27003
+ evaluations: composition.evaluations,
27004
+ trades: composition.trades,
27005
+ lineageScopes: composition.lineageScopes
27006
+ });
27007
+ const deployment = resolveRuntimeEvidenceTickerUniverse({
27008
+ deployment: composition.deployment,
27009
+ lineageScopes: composition.lineageScopes
27010
+ });
27011
+ return {
27012
+ artifact: {
27013
+ reportType: "runtime-evidence",
27014
+ generatedAt: Date.now(),
27015
+ ...composition.producer ? { producer: composition.producer } : {},
27016
+ ...producer && composition.producer && composition.producer !== producer ? { collector: producer } : {},
27017
+ userName: flags27.user,
27018
+ window: {
27019
+ startTime,
27020
+ endTime,
27021
+ startIso: new Date(startTime).toISOString(),
27022
+ endIso: new Date(endTime).toISOString(),
27023
+ source
27024
+ },
27025
+ strategies,
27026
+ deployment,
27027
+ runtime
27028
+ },
27029
+ composition
27030
+ };
27031
+ });
27032
+ const current = artifacts.find(
27033
+ ({ artifact: artifact2 }) => artifact2.deployment.deploymentCompositionId === deploymentSnapshot.deploymentCompositionId
27034
+ ) ?? artifacts.at(-1);
27035
+ if (!current) {
27036
+ throw new Error("Runtime evidence window has no versioned runtime rows");
27037
+ }
27038
+ const artifact = current.artifact;
26893
27039
  const outPath = import_node_path24.default.resolve(projectRoot11, String(flags27.out));
26894
27040
  await import_promises28.default.mkdir(import_node_path24.default.dirname(outPath), { recursive: true });
26895
27041
  await import_promises28.default.writeFile(outPath, `${JSON.stringify(artifact, null, 2)}
26896
27042
  `, "utf8");
26897
- const published = publishDir ? await publishRuntimeEvidenceBundle({
26898
- publishRoot: import_node_path24.default.resolve(projectRoot11, publishDir),
26899
- deploymentId,
26900
- userName: String(flags27.user),
26901
- startTime,
26902
- endTime,
26903
- artifact,
26904
- counts: artifact.runtime.counts,
26905
- lineageKeys: [
26906
- ...currentSignals.map((signal) => signal.runtimeLineage),
26907
- ...currentEvaluations.map((evaluation) => evaluation.runtimeLineage),
26908
- ...currentTrades.map((trade) => trade.runtimeLineage),
26909
- ...currentLineageScopes.map((scope) => scope.lineage)
26910
- ].filter((lineage) => lineage != null).map(runtimeLineageKey).filter((key, index, values) => values.indexOf(key) === index)
26911
- }) : null;
27043
+ const published = publishDir ? await Promise.all(
27044
+ artifacts.map(
27045
+ ({ artifact: artifact2, composition }) => publishRuntimeEvidenceBundle({
27046
+ publishRoot: import_node_path24.default.resolve(projectRoot11, publishDir),
27047
+ deploymentId,
27048
+ userName: String(flags27.user),
27049
+ startTime,
27050
+ endTime,
27051
+ artifact: artifact2,
27052
+ counts: artifact2.runtime.counts,
27053
+ lineageKeys: [
27054
+ ...composition.signals.map((signal) => signal.runtimeLineage),
27055
+ ...composition.evaluations.map(
27056
+ (evaluation) => evaluation.runtimeLineage
27057
+ ),
27058
+ ...composition.trades.map((trade) => trade.runtimeLineage),
27059
+ ...composition.lineageScopes.map((scope) => scope.lineage)
27060
+ ].filter((lineage) => lineage != null).map(runtimeLineageKey).filter((key, index, values) => values.indexOf(key) === index)
27061
+ })
27062
+ )
27063
+ ) : [];
26912
27064
  console.log(import_chalk41.default.green(`Wrote ${outPath}`));
26913
- if (published) {
26914
- console.log(import_chalk41.default.green(`Published ${published.bundleDir}`));
27065
+ for (const bundle of published) {
27066
+ console.log(import_chalk41.default.green(`Published ${bundle.bundleDir}`));
26915
27067
  }
26916
27068
  console.log(
26917
27069
  JSON.stringify(
26918
27070
  {
26919
- strategies: artifact.strategies,
26920
- runtimeCounts: artifact.runtime.counts
27071
+ compositions: artifacts.map(({ artifact: artifact2 }) => ({
27072
+ deploymentCompositionId: artifact2.deployment.deploymentCompositionId,
27073
+ strategies: artifact2.strategies,
27074
+ runtimeCounts: artifact2.runtime.counts
27075
+ }))
26921
27076
  },
26922
27077
  null,
26923
27078
  2
@@ -0,0 +1,464 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/lib/runtimeEvidenceCompositions.ts
31
+ var runtimeEvidenceCompositions_exports = {};
32
+ __export(runtimeEvidenceCompositions_exports, {
33
+ loadRuntimeEvidenceCompositionSnapshots: () => loadRuntimeEvidenceCompositionSnapshots,
34
+ splitRuntimeEvidenceByComposition: () => splitRuntimeEvidenceByComposition
35
+ });
36
+ module.exports = __toCommonJS(runtimeEvidenceCompositions_exports);
37
+
38
+ // src/lib/runtimeEvidenceArtifacts.ts
39
+ var import_node_crypto = require("crypto");
40
+ var import_promises = __toESM(require("fs/promises"));
41
+ var import_node_path = __toESM(require("path"));
42
+
43
+ // src/lib/runtimeEvidenceDeployment.ts
44
+ var import_runtimeStrategies = require("@tradejs/node/runtimeStrategies");
45
+ var isRecord = (value) => value != null && typeof value === "object" && !Array.isArray(value);
46
+ var isNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0;
47
+ var isPackageVersionMap = (value) => isRecord(value) && Object.keys(value).length > 0 && Object.entries(value).every(
48
+ ([name, version]) => name.startsWith("@tradejs/") && isNonEmptyString(version)
49
+ );
50
+ var INTERVALS = /* @__PURE__ */ new Set([
51
+ "1",
52
+ "3",
53
+ "5",
54
+ "15",
55
+ "30",
56
+ "60",
57
+ "120",
58
+ "240",
59
+ "360",
60
+ "720",
61
+ "D",
62
+ "W",
63
+ "M"
64
+ ]);
65
+ var UNIVERSES = /* @__PURE__ */ new Set(["crypto", "tradfi"]);
66
+ var isRuntimeStrategySelection = (value) => value === void 0 || isRecord(value) && Object.keys(value).length === 1 && Object.keys(value).every((key) => key === "tickers") && Array.isArray(value.tickers) && value.tickers.length > 0 && value.tickers.every((ticker) => isNonEmptyString(ticker));
67
+ var samePackageVersions = (left, right) => {
68
+ if (!left || !right) return false;
69
+ const leftEntries = Object.entries(left).sort(
70
+ ([a], [b]) => a.localeCompare(b)
71
+ );
72
+ const rightEntries = Object.entries(right).sort(
73
+ ([a], [b]) => a.localeCompare(b)
74
+ );
75
+ return JSON.stringify(leftEntries) === JSON.stringify(rightEntries);
76
+ };
77
+ var runtimeLineageMatchesStrategySnapshot = ({
78
+ lineage,
79
+ deployment,
80
+ strategy
81
+ }) => lineage?.schemaVersion === 3 && lineage.strategyRevision === strategy.strategyRevision && lineage.deploymentCompositionId === deployment.deploymentCompositionId && lineage.strategyPackageVersion === strategy.strategyPackageVersion && lineage.runtimePackageVersion === strategy.runtimePackageVersion && samePackageVersions(
82
+ lineage.strategyDependencyVersions,
83
+ strategy.strategyDependencyVersions
84
+ );
85
+ var parseRuntimeEvidenceDeploymentSnapshot = (value) => {
86
+ if (!isRecord(value) || value.schemaVersion !== 2 || !isNonEmptyString(value.id) || !isNonEmptyString(value.deploymentCompositionId) || !/^dc1:[a-f0-9]{16}$/.test(value.deploymentCompositionId) || !isNonEmptyString(value.label) || !isNonEmptyString(value.connectorName) || !isNonEmptyString(value.provider) || !isNonEmptyString(value.accountId) || typeof value.enabled !== "boolean" || !Array.isArray(value.strategies)) {
87
+ throw new Error(
88
+ "Runtime evidence deployment snapshot is missing or invalid"
89
+ );
90
+ }
91
+ const strategies = value.strategies.map((candidate) => {
92
+ if (!isRecord(candidate) || !isNonEmptyString(candidate.strategyName) || !isNonEmptyString(candidate.strategyRevision) || !/^sr1:[a-f0-9]{16}$/.test(candidate.strategyRevision) || typeof candidate.enabled !== "boolean" || !["active", "entries_paused"].includes(String(candidate.controlState)) || !INTERVALS.has(String(candidate.interval)) || !UNIVERSES.has(String(candidate.universe)) || !isNonEmptyString(candidate.strategyPackage) || !isNonEmptyString(candidate.strategyPackageVersion) || !isPackageVersionMap(candidate.strategyDependencyVersions) || !isNonEmptyString(candidate.runtimePackageVersion) || !isRecord(candidate.strategyConfig) || !isRuntimeStrategySelection(candidate.selection)) {
93
+ throw new Error(
94
+ "Runtime evidence deployment strategy snapshot is invalid"
95
+ );
96
+ }
97
+ if (String(candidate.strategyConfig.INTERVAL) !== String(candidate.interval) || String(candidate.strategyConfig.UNIVERSE) !== String(candidate.universe)) {
98
+ throw new Error(
99
+ `Runtime evidence strategy config does not match its scope: ${candidate.strategyName}`
100
+ );
101
+ }
102
+ if (candidate.accountId != null && (!isNonEmptyString(candidate.accountId) || candidate.accountId !== value.accountId)) {
103
+ throw new Error(
104
+ `Runtime evidence strategy account does not match deployment: ${candidate.strategyName}`
105
+ );
106
+ }
107
+ return candidate;
108
+ });
109
+ const identities = /* @__PURE__ */ new Set();
110
+ for (const strategy of strategies) {
111
+ if (identities.has(strategy.strategyName)) {
112
+ throw new Error(
113
+ `Duplicate runtime evidence strategy: ${strategy.strategyName}`
114
+ );
115
+ }
116
+ identities.add(strategy.strategyName);
117
+ }
118
+ return {
119
+ ...value,
120
+ strategies
121
+ };
122
+ };
123
+ var activeRuntimeEvidenceStrategies = (deployment) => deployment.enabled ? deployment.strategies.filter((strategy) => strategy.enabled) : [];
124
+ var unwrapRuntimeEvidenceRows = (value, field) => (Array.isArray(value) ? value : []).map((candidate) => {
125
+ const record = isRecord(candidate) ? candidate : null;
126
+ const nested = record && isRecord(record[field]) ? record[field] : null;
127
+ return nested ?? record;
128
+ }).filter((record) => record != null);
129
+ var assertCurrentRuntimeEvidenceArtifact = (artifact) => {
130
+ if (!isRecord(artifact) || artifact.reportType !== "runtime-evidence") {
131
+ throw new Error("Invalid runtime evidence artifact");
132
+ }
133
+ const deployment = parseRuntimeEvidenceDeploymentSnapshot(
134
+ artifact.deployment
135
+ );
136
+ const activeStrategies = activeRuntimeEvidenceStrategies(deployment);
137
+ if (activeStrategies.length === 0) {
138
+ throw new Error("Runtime evidence deployment has no enabled strategies");
139
+ }
140
+ const runtime = isRecord(artifact.runtime) ? artifact.runtime : null;
141
+ if (!runtime) {
142
+ throw new Error("Runtime evidence payload is missing");
143
+ }
144
+ if ("strategyConfigs" in runtime) {
145
+ throw new Error(
146
+ "Runtime evidence payload contains removed strategyConfigs data"
147
+ );
148
+ }
149
+ if ("evaluationStatsBuckets" in runtime) {
150
+ throw new Error(
151
+ "Runtime evidence payload contains removed lineage-less evaluationStatsBuckets data"
152
+ );
153
+ }
154
+ const rowFields = [
155
+ "trades",
156
+ "signals",
157
+ "evaluations",
158
+ "lineageScopes"
159
+ ];
160
+ for (const field of rowFields) {
161
+ if (!Array.isArray(runtime[field])) {
162
+ throw new Error(`Runtime evidence payload ${field} must be an array`);
163
+ }
164
+ }
165
+ const snapshots = new Map(
166
+ activeStrategies.map((strategy) => [strategy.strategyName, strategy])
167
+ );
168
+ const assertRows = (label, rows, lineageField) => {
169
+ for (const row of rows) {
170
+ const strategyName = String(row.strategy ?? "").trim();
171
+ const strategy = snapshots.get(strategyName);
172
+ if (!strategy || row.deploymentId !== deployment.id || row.accountId !== deployment.accountId || !runtimeLineageMatchesStrategySnapshot({
173
+ lineage: isRecord(row[lineageField]) ? row[lineageField] : void 0,
174
+ deployment,
175
+ strategy
176
+ })) {
177
+ throw new Error(
178
+ `Runtime evidence ${label} row is outside the current embedded deployment lineage`
179
+ );
180
+ }
181
+ }
182
+ };
183
+ assertRows(
184
+ "trade",
185
+ unwrapRuntimeEvidenceRows(runtime.trades, "trade"),
186
+ "runtimeLineage"
187
+ );
188
+ assertRows(
189
+ "signal",
190
+ unwrapRuntimeEvidenceRows(runtime.signals, "signal"),
191
+ "runtimeLineage"
192
+ );
193
+ assertRows(
194
+ "evaluation",
195
+ unwrapRuntimeEvidenceRows(runtime.evaluations, "evaluation"),
196
+ "runtimeLineage"
197
+ );
198
+ assertRows(
199
+ "lineage scope",
200
+ unwrapRuntimeEvidenceRows(runtime.lineageScopes, "lineageScope"),
201
+ "lineage"
202
+ );
203
+ return deployment;
204
+ };
205
+
206
+ // src/lib/runtimeEvidenceArtifacts.ts
207
+ var RUNTIME_EVIDENCE_MANIFEST_FILE = "manifest.json";
208
+ var RUNTIME_EVIDENCE_PAYLOAD_FILE = "runtime-evidence.json";
209
+ var RUNTIME_EVIDENCE_COMPLETE_FILE = ".complete";
210
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
211
+ var sha256 = (value) => (0, import_node_crypto.createHash)("sha256").update(value).digest("hex");
212
+ var assertFiniteTimestamp = (value, label) => {
213
+ if (typeof value !== "number" || !Number.isFinite(value)) {
214
+ throw new Error(`Invalid runtime evidence ${label}`);
215
+ }
216
+ return value;
217
+ };
218
+ var parseManifest = (value) => {
219
+ const manifest = asRecord(value);
220
+ const window = asRecord(manifest?.window);
221
+ const payload = asRecord(manifest?.payload);
222
+ const counts = asRecord(manifest?.counts);
223
+ if (manifest?.schemaVersion !== 1 || manifest.reportType !== "runtime-evidence" || typeof manifest.artifactId !== "string" || typeof manifest.deploymentId !== "string" || typeof manifest.userName !== "string" || typeof manifest.createdAt !== "number" || payload?.file !== RUNTIME_EVIDENCE_PAYLOAD_FILE || typeof payload.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(payload.sha256) || typeof payload.bytes !== "number" || !Array.isArray(manifest.lineageKeys)) {
224
+ throw new Error("Invalid runtime evidence manifest");
225
+ }
226
+ return {
227
+ schemaVersion: 1,
228
+ artifactId: manifest.artifactId,
229
+ reportType: "runtime-evidence",
230
+ deploymentId: manifest.deploymentId,
231
+ userName: manifest.userName,
232
+ createdAt: manifest.createdAt,
233
+ window: {
234
+ startTime: assertFiniteTimestamp(window?.startTime, "window.startTime"),
235
+ endTime: assertFiniteTimestamp(window?.endTime, "window.endTime")
236
+ },
237
+ payload: {
238
+ file: RUNTIME_EVIDENCE_PAYLOAD_FILE,
239
+ sha256: payload.sha256,
240
+ bytes: payload.bytes
241
+ },
242
+ counts: Object.fromEntries(
243
+ Object.entries(counts ?? {}).filter(
244
+ (entry) => typeof entry[1] === "number" && Number.isFinite(entry[1])
245
+ )
246
+ ),
247
+ lineageKeys: manifest.lineageKeys.filter(
248
+ (value2) => typeof value2 === "string" && value2.length > 0
249
+ )
250
+ };
251
+ };
252
+ var verifyRuntimeEvidenceBundle = async (bundleDir) => {
253
+ const manifestPath = import_node_path.default.join(bundleDir, RUNTIME_EVIDENCE_MANIFEST_FILE);
254
+ const payloadPath = import_node_path.default.join(bundleDir, RUNTIME_EVIDENCE_PAYLOAD_FILE);
255
+ const completePath = import_node_path.default.join(bundleDir, RUNTIME_EVIDENCE_COMPLETE_FILE);
256
+ const [manifestText, payload, completeText] = await Promise.all([
257
+ import_promises.default.readFile(manifestPath, "utf8"),
258
+ import_promises.default.readFile(payloadPath),
259
+ import_promises.default.readFile(completePath, "utf8")
260
+ ]);
261
+ const manifest = parseManifest(JSON.parse(manifestText));
262
+ if (completeText.trim() !== manifest.artifactId) {
263
+ throw new Error(`Invalid completion marker in ${bundleDir}`);
264
+ }
265
+ if (payload.byteLength !== manifest.payload.bytes) {
266
+ throw new Error(`Runtime evidence size mismatch in ${bundleDir}`);
267
+ }
268
+ if (sha256(payload) !== manifest.payload.sha256) {
269
+ throw new Error(`Runtime evidence checksum mismatch in ${bundleDir}`);
270
+ }
271
+ const artifact = asRecord(JSON.parse(payload.toString("utf8")));
272
+ const artifactWindow = asRecord(artifact?.window);
273
+ if (artifact?.reportType !== "runtime-evidence" || artifactWindow?.startTime !== manifest.window.startTime || artifactWindow.endTime !== manifest.window.endTime) {
274
+ throw new Error(`Runtime evidence payload mismatch in ${bundleDir}`);
275
+ }
276
+ const deployment = assertCurrentRuntimeEvidenceArtifact(artifact);
277
+ if (deployment.id !== manifest.deploymentId) {
278
+ throw new Error(`Runtime evidence deployment mismatch in ${bundleDir}`);
279
+ }
280
+ return {
281
+ bundleDir,
282
+ manifestPath,
283
+ payloadPath,
284
+ manifest,
285
+ artifact
286
+ };
287
+ };
288
+ var discoverRuntimeEvidenceBundles = async (rootDir) => {
289
+ const bundles = [];
290
+ const visit = async (directory, depth) => {
291
+ if (depth > 8) return;
292
+ let entries;
293
+ try {
294
+ entries = await import_promises.default.readdir(directory, { withFileTypes: true });
295
+ } catch (error) {
296
+ if (error.code === "ENOENT") return;
297
+ throw error;
298
+ }
299
+ const names = new Set(entries.map((entry) => entry.name));
300
+ if (names.has(RUNTIME_EVIDENCE_MANIFEST_FILE) && names.has(RUNTIME_EVIDENCE_PAYLOAD_FILE) && names.has(RUNTIME_EVIDENCE_COMPLETE_FILE)) {
301
+ bundles.push(directory);
302
+ return;
303
+ }
304
+ await Promise.all(
305
+ entries.filter(
306
+ (entry) => entry.isDirectory() && !entry.name.startsWith(".") && !entry.name.includes(".tmp-")
307
+ ).map((entry) => visit(import_node_path.default.join(directory, entry.name), depth + 1))
308
+ );
309
+ };
310
+ await visit(rootDir, 0);
311
+ return bundles.sort();
312
+ };
313
+
314
+ // src/lib/runtimeEvidenceProducer.ts
315
+ var import_node_crypto2 = require("crypto");
316
+ var import_promises2 = __toESM(require("fs/promises"));
317
+ var import_node_path2 = __toESM(require("path"));
318
+ var isRecord2 = (value) => value != null && typeof value === "object" && !Array.isArray(value);
319
+ var parseRuntimeEvidenceProducer = (value) => {
320
+ const producer = isRecord2(value) ? value : null;
321
+ const runtimePackageManifest = isRecord2(producer?.runtimePackageManifest) ? producer.runtimePackageManifest : null;
322
+ if (producer?.schemaVersion !== 1 || typeof producer.projectSha !== "string" || !/^[a-f0-9]{40}$/.test(producer.projectSha) || typeof producer.imageDigest !== "string" || !/^sha256:[a-f0-9]{64}$/.test(producer.imageDigest) || runtimePackageManifest?.file !== "runtime-package-manifest.json" || typeof runtimePackageManifest.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(runtimePackageManifest.sha256)) {
323
+ throw new Error("Runtime evidence producer image identity is invalid");
324
+ }
325
+ return producer;
326
+ };
327
+
328
+ // src/lib/runtimeEvidenceCompositions.ts
329
+ var stableSnapshotIdentity = (deployment) => JSON.stringify({ ...deployment, tickers: void 0 });
330
+ var producerIdentity = (producer) => JSON.stringify(producer);
331
+ var loadRuntimeEvidenceCompositionSnapshots = async ({
332
+ publishRoot,
333
+ currentDeployment,
334
+ currentProducer
335
+ }) => {
336
+ const snapshots = /* @__PURE__ */ new Map();
337
+ const register = (snapshot) => {
338
+ const compositionId = snapshot.deployment.deploymentCompositionId;
339
+ const known = snapshots.get(compositionId);
340
+ if (known && (stableSnapshotIdentity(known.deployment) !== stableSnapshotIdentity(snapshot.deployment) || producerIdentity(known.producer) !== producerIdentity(snapshot.producer))) {
341
+ throw new Error(
342
+ `Conflicting runtime evidence snapshot for composition ${compositionId}`
343
+ );
344
+ }
345
+ snapshots.set(compositionId, snapshot);
346
+ };
347
+ const bundleDirs = await discoverRuntimeEvidenceBundles(publishRoot);
348
+ for (const bundleDir of bundleDirs) {
349
+ const bundle = await verifyRuntimeEvidenceBundle(bundleDir);
350
+ const deployment = parseRuntimeEvidenceDeploymentSnapshot(
351
+ bundle.artifact.deployment
352
+ );
353
+ if (deployment.id !== currentDeployment.id) continue;
354
+ if (bundle.artifact.producer == null) continue;
355
+ register({
356
+ deployment: { ...deployment, tickers: void 0 },
357
+ producer: parseRuntimeEvidenceProducer(bundle.artifact.producer)
358
+ });
359
+ }
360
+ const historicalCurrent = snapshots.get(
361
+ currentDeployment.deploymentCompositionId
362
+ );
363
+ if (historicalCurrent && stableSnapshotIdentity(historicalCurrent.deployment) !== stableSnapshotIdentity(currentDeployment)) {
364
+ throw new Error(
365
+ `Current runtime deployment conflicts with stored composition ${currentDeployment.deploymentCompositionId}`
366
+ );
367
+ }
368
+ snapshots.set(currentDeployment.deploymentCompositionId, {
369
+ deployment: { ...currentDeployment, tickers: void 0 },
370
+ producer: currentProducer
371
+ });
372
+ return snapshots;
373
+ };
374
+ var rowCompositionId = (row) => row.runtimeLineage?.deploymentCompositionId ?? row.lineage?.deploymentCompositionId ?? null;
375
+ var rowTimestamp = (row) => {
376
+ for (const field of [
377
+ "firstTimestamp",
378
+ "entryTimestamp",
379
+ "timestamp",
380
+ "lastTimestamp"
381
+ ]) {
382
+ const value = row[field];
383
+ if (typeof value === "number" && Number.isFinite(value)) return value;
384
+ }
385
+ return Number.POSITIVE_INFINITY;
386
+ };
387
+ var belongsToSnapshot = (row, deployment) => {
388
+ const strategy = activeRuntimeEvidenceStrategies(deployment).find(
389
+ (candidate) => candidate.strategyName === row.strategy
390
+ );
391
+ return Boolean(
392
+ strategy && row.deploymentId === deployment.id && row.accountId === deployment.accountId && runtimeLineageMatchesStrategySnapshot({
393
+ lineage: row.runtimeLineage ?? row.lineage,
394
+ deployment,
395
+ strategy
396
+ })
397
+ );
398
+ };
399
+ var splitRuntimeEvidenceByComposition = ({
400
+ evidence,
401
+ snapshots,
402
+ includeEmptyCompositionIds = [],
403
+ ignoreUnknownCompositions = false
404
+ }) => {
405
+ const observed = /* @__PURE__ */ new Set();
406
+ for (const row of [
407
+ ...evidence.trades,
408
+ ...evidence.signals,
409
+ ...evidence.evaluations,
410
+ ...evidence.lineageScopes
411
+ ]) {
412
+ const compositionId = rowCompositionId(row);
413
+ if (compositionId) observed.add(compositionId);
414
+ }
415
+ for (const compositionId of includeEmptyCompositionIds) {
416
+ observed.add(compositionId);
417
+ }
418
+ const missing = [...observed].filter(
419
+ (compositionId) => !snapshots.has(compositionId)
420
+ );
421
+ if (missing.length && !ignoreUnknownCompositions) {
422
+ throw new Error(
423
+ `Runtime evidence has no verified deployment snapshot for composition(s): ${missing.sort().join(", ")}`
424
+ );
425
+ }
426
+ return [...observed].filter((compositionId) => snapshots.has(compositionId)).map((compositionId) => snapshots.get(compositionId)).map(({ deployment, producer }) => {
427
+ const trades = evidence.trades.filter(
428
+ (row) => belongsToSnapshot(row, deployment)
429
+ );
430
+ const signals = evidence.signals.filter(
431
+ (row) => belongsToSnapshot(row, deployment)
432
+ );
433
+ const evaluations = evidence.evaluations.filter(
434
+ (row) => belongsToSnapshot(row, deployment)
435
+ );
436
+ const lineageScopes = evidence.lineageScopes.filter(
437
+ (row) => belongsToSnapshot(row, deployment)
438
+ );
439
+ const timestamps = [
440
+ ...trades,
441
+ ...signals,
442
+ ...evaluations,
443
+ ...lineageScopes
444
+ ].map((row) => rowTimestamp(row));
445
+ return {
446
+ deployment,
447
+ producer,
448
+ trades,
449
+ signals,
450
+ evaluations,
451
+ lineageScopes,
452
+ firstTimestamp: Math.min(...timestamps)
453
+ };
454
+ }).sort(
455
+ (left, right) => left.firstTimestamp - right.firstTimestamp || left.deployment.deploymentCompositionId.localeCompare(
456
+ right.deployment.deploymentCompositionId
457
+ )
458
+ );
459
+ };
460
+ // Annotate the CommonJS export names for ESM import in node:
461
+ 0 && (module.exports = {
462
+ loadRuntimeEvidenceCompositionSnapshots,
463
+ splitRuntimeEvidenceByComposition
464
+ });
@@ -851,35 +851,46 @@ var verifyRuntimeEvidenceBundle = async (bundleDir) => {
851
851
  artifact
852
852
  };
853
853
  };
854
-
855
- // src/lib/runtimeLineage.ts
856
- var import_node_crypto2 = require("crypto");
857
- var normalizeForStableJson = (value) => {
858
- if (Array.isArray(value)) {
859
- return value.map(normalizeForStableJson);
860
- }
861
- if (value && typeof value === "object") {
862
- return Object.fromEntries(
863
- Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalizeForStableJson(entry)])
854
+ var discoverRuntimeEvidenceBundles = async (rootDir) => {
855
+ const bundles = [];
856
+ const visit = async (directory, depth) => {
857
+ if (depth > 8) return;
858
+ let entries;
859
+ try {
860
+ entries = await import_promises.default.readdir(directory, { withFileTypes: true });
861
+ } catch (error) {
862
+ if (error.code === "ENOENT") return;
863
+ throw error;
864
+ }
865
+ const names = new Set(entries.map((entry) => entry.name));
866
+ if (names.has(RUNTIME_EVIDENCE_MANIFEST_FILE) && names.has(RUNTIME_EVIDENCE_PAYLOAD_FILE) && names.has(RUNTIME_EVIDENCE_COMPLETE_FILE)) {
867
+ bundles.push(directory);
868
+ return;
869
+ }
870
+ await Promise.all(
871
+ entries.filter(
872
+ (entry) => entry.isDirectory() && !entry.name.startsWith(".") && !entry.name.includes(".tmp-")
873
+ ).map((entry) => visit(import_node_path.default.join(directory, entry.name), depth + 1))
864
874
  );
865
- }
866
- return value;
875
+ };
876
+ await visit(rootDir, 0);
877
+ return bundles.sort();
867
878
  };
868
- var fingerprintRuntimeValue = (value) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
869
- var runtimeLineageKey = (lineage) => [
870
- "v3",
871
- lineage.deploymentCompositionId,
872
- lineage.strategyRevision,
873
- lineage.strategyPackageVersion,
874
- `deps:${fingerprintRuntimeValue(lineage.strategyDependencyVersions).slice(0, 16)}`,
875
- lineage.runtimePackageVersion
876
- ].join(":");
877
879
 
878
880
  // src/lib/runtimeEvidenceProducer.ts
879
- var import_node_crypto3 = require("crypto");
881
+ var import_node_crypto2 = require("crypto");
880
882
  var import_promises2 = __toESM(require("fs/promises"));
881
883
  var import_node_path2 = __toESM(require("path"));
882
- var sha2562 = (value) => (0, import_node_crypto3.createHash)("sha256").update(value).digest("hex");
884
+ var isRecord2 = (value) => value != null && typeof value === "object" && !Array.isArray(value);
885
+ var sha2562 = (value) => (0, import_node_crypto2.createHash)("sha256").update(value).digest("hex");
886
+ var parseRuntimeEvidenceProducer = (value) => {
887
+ const producer = isRecord2(value) ? value : null;
888
+ const runtimePackageManifest = isRecord2(producer?.runtimePackageManifest) ? producer.runtimePackageManifest : null;
889
+ if (producer?.schemaVersion !== 1 || typeof producer.projectSha !== "string" || !/^[a-f0-9]{40}$/.test(producer.projectSha) || typeof producer.imageDigest !== "string" || !/^sha256:[a-f0-9]{64}$/.test(producer.imageDigest) || runtimePackageManifest?.file !== "runtime-package-manifest.json" || typeof runtimePackageManifest.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(runtimePackageManifest.sha256)) {
890
+ throw new Error("Runtime evidence producer image identity is invalid");
891
+ }
892
+ return producer;
893
+ };
883
894
  var resolveRuntimeEvidenceProducer = async ({
884
895
  projectRoot: projectRoot2,
885
896
  required
@@ -925,6 +936,162 @@ var resolveRuntimeEvidenceProducer = async ({
925
936
  };
926
937
  };
927
938
 
939
+ // src/lib/runtimeEvidenceCompositions.ts
940
+ var stableSnapshotIdentity = (deployment) => JSON.stringify({ ...deployment, tickers: void 0 });
941
+ var producerIdentity = (producer) => JSON.stringify(producer);
942
+ var loadRuntimeEvidenceCompositionSnapshots = async ({
943
+ publishRoot,
944
+ currentDeployment,
945
+ currentProducer
946
+ }) => {
947
+ const snapshots = /* @__PURE__ */ new Map();
948
+ const register = (snapshot) => {
949
+ const compositionId = snapshot.deployment.deploymentCompositionId;
950
+ const known = snapshots.get(compositionId);
951
+ if (known && (stableSnapshotIdentity(known.deployment) !== stableSnapshotIdentity(snapshot.deployment) || producerIdentity(known.producer) !== producerIdentity(snapshot.producer))) {
952
+ throw new Error(
953
+ `Conflicting runtime evidence snapshot for composition ${compositionId}`
954
+ );
955
+ }
956
+ snapshots.set(compositionId, snapshot);
957
+ };
958
+ const bundleDirs = await discoverRuntimeEvidenceBundles(publishRoot);
959
+ for (const bundleDir of bundleDirs) {
960
+ const bundle = await verifyRuntimeEvidenceBundle(bundleDir);
961
+ const deployment = parseRuntimeEvidenceDeploymentSnapshot(
962
+ bundle.artifact.deployment
963
+ );
964
+ if (deployment.id !== currentDeployment.id) continue;
965
+ if (bundle.artifact.producer == null) continue;
966
+ register({
967
+ deployment: { ...deployment, tickers: void 0 },
968
+ producer: parseRuntimeEvidenceProducer(bundle.artifact.producer)
969
+ });
970
+ }
971
+ const historicalCurrent = snapshots.get(
972
+ currentDeployment.deploymentCompositionId
973
+ );
974
+ if (historicalCurrent && stableSnapshotIdentity(historicalCurrent.deployment) !== stableSnapshotIdentity(currentDeployment)) {
975
+ throw new Error(
976
+ `Current runtime deployment conflicts with stored composition ${currentDeployment.deploymentCompositionId}`
977
+ );
978
+ }
979
+ snapshots.set(currentDeployment.deploymentCompositionId, {
980
+ deployment: { ...currentDeployment, tickers: void 0 },
981
+ producer: currentProducer
982
+ });
983
+ return snapshots;
984
+ };
985
+ var rowCompositionId = (row) => row.runtimeLineage?.deploymentCompositionId ?? row.lineage?.deploymentCompositionId ?? null;
986
+ var rowTimestamp = (row) => {
987
+ for (const field of [
988
+ "firstTimestamp",
989
+ "entryTimestamp",
990
+ "timestamp",
991
+ "lastTimestamp"
992
+ ]) {
993
+ const value = row[field];
994
+ if (typeof value === "number" && Number.isFinite(value)) return value;
995
+ }
996
+ return Number.POSITIVE_INFINITY;
997
+ };
998
+ var belongsToSnapshot = (row, deployment) => {
999
+ const strategy = activeRuntimeEvidenceStrategies(deployment).find(
1000
+ (candidate) => candidate.strategyName === row.strategy
1001
+ );
1002
+ return Boolean(
1003
+ strategy && row.deploymentId === deployment.id && row.accountId === deployment.accountId && runtimeLineageMatchesStrategySnapshot({
1004
+ lineage: row.runtimeLineage ?? row.lineage,
1005
+ deployment,
1006
+ strategy
1007
+ })
1008
+ );
1009
+ };
1010
+ var splitRuntimeEvidenceByComposition = ({
1011
+ evidence,
1012
+ snapshots,
1013
+ includeEmptyCompositionIds = [],
1014
+ ignoreUnknownCompositions = false
1015
+ }) => {
1016
+ const observed = /* @__PURE__ */ new Set();
1017
+ for (const row of [
1018
+ ...evidence.trades,
1019
+ ...evidence.signals,
1020
+ ...evidence.evaluations,
1021
+ ...evidence.lineageScopes
1022
+ ]) {
1023
+ const compositionId = rowCompositionId(row);
1024
+ if (compositionId) observed.add(compositionId);
1025
+ }
1026
+ for (const compositionId of includeEmptyCompositionIds) {
1027
+ observed.add(compositionId);
1028
+ }
1029
+ const missing = [...observed].filter(
1030
+ (compositionId) => !snapshots.has(compositionId)
1031
+ );
1032
+ if (missing.length && !ignoreUnknownCompositions) {
1033
+ throw new Error(
1034
+ `Runtime evidence has no verified deployment snapshot for composition(s): ${missing.sort().join(", ")}`
1035
+ );
1036
+ }
1037
+ return [...observed].filter((compositionId) => snapshots.has(compositionId)).map((compositionId) => snapshots.get(compositionId)).map(({ deployment, producer }) => {
1038
+ const trades = evidence.trades.filter(
1039
+ (row) => belongsToSnapshot(row, deployment)
1040
+ );
1041
+ const signals = evidence.signals.filter(
1042
+ (row) => belongsToSnapshot(row, deployment)
1043
+ );
1044
+ const evaluations = evidence.evaluations.filter(
1045
+ (row) => belongsToSnapshot(row, deployment)
1046
+ );
1047
+ const lineageScopes = evidence.lineageScopes.filter(
1048
+ (row) => belongsToSnapshot(row, deployment)
1049
+ );
1050
+ const timestamps = [
1051
+ ...trades,
1052
+ ...signals,
1053
+ ...evaluations,
1054
+ ...lineageScopes
1055
+ ].map((row) => rowTimestamp(row));
1056
+ return {
1057
+ deployment,
1058
+ producer,
1059
+ trades,
1060
+ signals,
1061
+ evaluations,
1062
+ lineageScopes,
1063
+ firstTimestamp: Math.min(...timestamps)
1064
+ };
1065
+ }).sort(
1066
+ (left, right) => left.firstTimestamp - right.firstTimestamp || left.deployment.deploymentCompositionId.localeCompare(
1067
+ right.deployment.deploymentCompositionId
1068
+ )
1069
+ );
1070
+ };
1071
+
1072
+ // src/lib/runtimeLineage.ts
1073
+ var import_node_crypto3 = require("crypto");
1074
+ var normalizeForStableJson = (value) => {
1075
+ if (Array.isArray(value)) {
1076
+ return value.map(normalizeForStableJson);
1077
+ }
1078
+ if (value && typeof value === "object") {
1079
+ return Object.fromEntries(
1080
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalizeForStableJson(entry)])
1081
+ );
1082
+ }
1083
+ return value;
1084
+ };
1085
+ var fingerprintRuntimeValue = (value) => (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
1086
+ var runtimeLineageKey = (lineage) => [
1087
+ "v3",
1088
+ lineage.deploymentCompositionId,
1089
+ lineage.strategyRevision,
1090
+ lineage.strategyPackageVersion,
1091
+ `deps:${fingerprintRuntimeValue(lineage.strategyDependencyVersions).slice(0, 16)}`,
1092
+ lineage.runtimePackageVersion
1093
+ ].join(":");
1094
+
928
1095
  // src/scripts/runtimeEvidence.ts
929
1096
  import_args.default.option(["u", "user"], "Use user config", "root");
930
1097
  import_args.default.option(
@@ -1015,15 +1182,30 @@ var runtimeEvidence = async () => {
1015
1182
  projectRoot,
1016
1183
  deploymentId
1017
1184
  });
1018
- const activeStrategies = activeRuntimeEvidenceStrategies(
1019
- deploymentSnapshot
1020
- ).map(({ strategyName }) => strategyName);
1021
1185
  const requestedStrategies = parseStrategyFilter(flags.strategy);
1022
- const strategies = requestedStrategies.length ? requestedStrategies : activeStrategies;
1186
+ const evidence = await collectRuntimeDebugEvidence({
1187
+ userName: flags.user,
1188
+ startTime,
1189
+ endTime,
1190
+ strategies: requestedStrategies.length ? requestedStrategies : void 0,
1191
+ deploymentId
1192
+ });
1193
+ const snapshots = publishDir && producer ? await loadRuntimeEvidenceCompositionSnapshots({
1194
+ publishRoot: import_node_path3.default.resolve(projectRoot, publishDir),
1195
+ currentDeployment: deploymentSnapshot,
1196
+ currentProducer: producer
1197
+ }) : /* @__PURE__ */ new Map([
1198
+ [
1199
+ deploymentSnapshot.deploymentCompositionId,
1200
+ { deployment: deploymentSnapshot, producer }
1201
+ ]
1202
+ ]);
1023
1203
  const declaredStrategies = new Set(
1024
- deploymentSnapshot.strategies.map(({ strategyName }) => strategyName)
1204
+ [...snapshots.values()].flatMap(
1205
+ ({ deployment }) => deployment.strategies.map(({ strategyName }) => strategyName)
1206
+ )
1025
1207
  );
1026
- const unknownStrategies = strategies.filter(
1208
+ const unknownStrategies = requestedStrategies.filter(
1027
1209
  (strategyName) => !declaredStrategies.has(strategyName)
1028
1210
  );
1029
1211
  if (unknownStrategies.length) {
@@ -1031,97 +1213,94 @@ var runtimeEvidence = async () => {
1031
1213
  `Strategies are not declared in ${deploymentId}: ${unknownStrategies.join(", ")}`
1032
1214
  );
1033
1215
  }
1034
- const evidence = await collectRuntimeDebugEvidence({
1035
- userName: flags.user,
1036
- startTime,
1037
- endTime,
1038
- strategies,
1039
- deploymentId
1216
+ const compositionRows = splitRuntimeEvidenceByComposition({
1217
+ evidence,
1218
+ snapshots,
1219
+ includeEmptyCompositionIds: publishDir ? [] : [deploymentSnapshot.deploymentCompositionId],
1220
+ ignoreUnknownCompositions: !publishDir
1040
1221
  });
1041
- const activeSnapshots = new Map(
1042
- activeRuntimeEvidenceStrategies(deploymentSnapshot).map((strategy) => [
1043
- strategy.strategyName,
1044
- strategy
1045
- ])
1046
- );
1047
- const belongsToCurrentDeployment = (row) => {
1048
- const snapshot = activeSnapshots.get(row.strategy);
1049
- return Boolean(
1050
- snapshot && row.deploymentId === deploymentSnapshot.id && row.accountId === deploymentSnapshot.accountId && runtimeLineageMatchesStrategySnapshot({
1051
- lineage: row.runtimeLineage,
1052
- deployment: deploymentSnapshot,
1053
- strategy: snapshot
1054
- })
1222
+ const artifacts = compositionRows.map((composition) => {
1223
+ const strategies = activeRuntimeEvidenceStrategies(composition.deployment).map(({ strategyName }) => strategyName).filter(
1224
+ (strategyName) => !requestedStrategies.length || requestedStrategies.includes(strategyName)
1055
1225
  );
1056
- };
1057
- const currentSignals = evidence.signals.filter(belongsToCurrentDeployment);
1058
- const currentEvaluations = evidence.evaluations.filter(
1059
- belongsToCurrentDeployment
1060
- );
1061
- const currentTrades = evidence.trades.filter(belongsToCurrentDeployment);
1062
- const currentLineageScopes = evidence.lineageScopes.filter(
1063
- (scope) => belongsToCurrentDeployment({
1064
- ...scope,
1065
- runtimeLineage: scope.lineage
1066
- })
1067
- );
1068
- const runtime = buildRuntimeEvidenceReportPayload({
1069
- userName: flags.user,
1070
- startTime,
1071
- endTime,
1072
- signals: currentSignals,
1073
- evaluations: currentEvaluations,
1074
- trades: currentTrades,
1075
- lineageScopes: currentLineageScopes
1076
- });
1077
- const deployment = resolveRuntimeEvidenceTickerUniverse({
1078
- deployment: deploymentSnapshot,
1079
- lineageScopes: currentLineageScopes
1080
- });
1081
- const artifact = {
1082
- reportType: "runtime-evidence",
1083
- generatedAt: Date.now(),
1084
- ...producer ? { producer } : {},
1085
- userName: flags.user,
1086
- window: {
1226
+ const runtime = buildRuntimeEvidenceReportPayload({
1227
+ userName: flags.user,
1087
1228
  startTime,
1088
1229
  endTime,
1089
- startIso: new Date(startTime).toISOString(),
1090
- endIso: new Date(endTime).toISOString(),
1091
- source
1092
- },
1093
- strategies,
1094
- deployment,
1095
- runtime
1096
- };
1230
+ signals: composition.signals,
1231
+ evaluations: composition.evaluations,
1232
+ trades: composition.trades,
1233
+ lineageScopes: composition.lineageScopes
1234
+ });
1235
+ const deployment = resolveRuntimeEvidenceTickerUniverse({
1236
+ deployment: composition.deployment,
1237
+ lineageScopes: composition.lineageScopes
1238
+ });
1239
+ return {
1240
+ artifact: {
1241
+ reportType: "runtime-evidence",
1242
+ generatedAt: Date.now(),
1243
+ ...composition.producer ? { producer: composition.producer } : {},
1244
+ ...producer && composition.producer && composition.producer !== producer ? { collector: producer } : {},
1245
+ userName: flags.user,
1246
+ window: {
1247
+ startTime,
1248
+ endTime,
1249
+ startIso: new Date(startTime).toISOString(),
1250
+ endIso: new Date(endTime).toISOString(),
1251
+ source
1252
+ },
1253
+ strategies,
1254
+ deployment,
1255
+ runtime
1256
+ },
1257
+ composition
1258
+ };
1259
+ });
1260
+ const current = artifacts.find(
1261
+ ({ artifact: artifact2 }) => artifact2.deployment.deploymentCompositionId === deploymentSnapshot.deploymentCompositionId
1262
+ ) ?? artifacts.at(-1);
1263
+ if (!current) {
1264
+ throw new Error("Runtime evidence window has no versioned runtime rows");
1265
+ }
1266
+ const artifact = current.artifact;
1097
1267
  const outPath = import_node_path3.default.resolve(projectRoot, String(flags.out));
1098
1268
  await import_promises3.default.mkdir(import_node_path3.default.dirname(outPath), { recursive: true });
1099
1269
  await import_promises3.default.writeFile(outPath, `${JSON.stringify(artifact, null, 2)}
1100
1270
  `, "utf8");
1101
- const published = publishDir ? await publishRuntimeEvidenceBundle({
1102
- publishRoot: import_node_path3.default.resolve(projectRoot, publishDir),
1103
- deploymentId,
1104
- userName: String(flags.user),
1105
- startTime,
1106
- endTime,
1107
- artifact,
1108
- counts: artifact.runtime.counts,
1109
- lineageKeys: [
1110
- ...currentSignals.map((signal) => signal.runtimeLineage),
1111
- ...currentEvaluations.map((evaluation) => evaluation.runtimeLineage),
1112
- ...currentTrades.map((trade) => trade.runtimeLineage),
1113
- ...currentLineageScopes.map((scope) => scope.lineage)
1114
- ].filter((lineage) => lineage != null).map(runtimeLineageKey).filter((key, index, values) => values.indexOf(key) === index)
1115
- }) : null;
1271
+ const published = publishDir ? await Promise.all(
1272
+ artifacts.map(
1273
+ ({ artifact: artifact2, composition }) => publishRuntimeEvidenceBundle({
1274
+ publishRoot: import_node_path3.default.resolve(projectRoot, publishDir),
1275
+ deploymentId,
1276
+ userName: String(flags.user),
1277
+ startTime,
1278
+ endTime,
1279
+ artifact: artifact2,
1280
+ counts: artifact2.runtime.counts,
1281
+ lineageKeys: [
1282
+ ...composition.signals.map((signal) => signal.runtimeLineage),
1283
+ ...composition.evaluations.map(
1284
+ (evaluation) => evaluation.runtimeLineage
1285
+ ),
1286
+ ...composition.trades.map((trade) => trade.runtimeLineage),
1287
+ ...composition.lineageScopes.map((scope) => scope.lineage)
1288
+ ].filter((lineage) => lineage != null).map(runtimeLineageKey).filter((key, index, values) => values.indexOf(key) === index)
1289
+ })
1290
+ )
1291
+ ) : [];
1116
1292
  console.log(import_chalk.default.green(`Wrote ${outPath}`));
1117
- if (published) {
1118
- console.log(import_chalk.default.green(`Published ${published.bundleDir}`));
1293
+ for (const bundle of published) {
1294
+ console.log(import_chalk.default.green(`Published ${bundle.bundleDir}`));
1119
1295
  }
1120
1296
  console.log(
1121
1297
  JSON.stringify(
1122
1298
  {
1123
- strategies: artifact.strategies,
1124
- runtimeCounts: artifact.runtime.counts
1299
+ compositions: artifacts.map(({ artifact: artifact2 }) => ({
1300
+ deploymentCompositionId: artifact2.deployment.deploymentCompositionId,
1301
+ strategies: artifact2.strategies,
1302
+ runtimeCounts: artifact2.runtime.counts
1303
+ }))
1125
1304
  },
1126
1305
  null,
1127
1306
  2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/cli",
3
- "version": "3.1.27-beta.247",
3
+ "version": "3.1.27-beta.249",
4
4
  "description": "Official CLI for the TradeJS TypeScript framework: infra setup, backtests, signals, bots, and ML workflows.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -36,13 +36,13 @@
36
36
  "tradejs": "dist/cli.js"
37
37
  },
38
38
  "dependencies": {
39
- "@tradejs/connectors": "^3.1.27-beta.247",
40
- "@tradejs/core": "^3.1.27-beta.247",
41
- "@tradejs/indicators": "^3.1.27-beta.247",
42
- "@tradejs/infra": "^3.1.27-beta.247",
43
- "@tradejs/node": "^3.1.27-beta.247",
39
+ "@tradejs/connectors": "^3.1.27-beta.249",
40
+ "@tradejs/core": "^3.1.27-beta.249",
41
+ "@tradejs/indicators": "^3.1.27-beta.249",
42
+ "@tradejs/infra": "^3.1.27-beta.249",
43
+ "@tradejs/node": "^3.1.27-beta.249",
44
44
  "@tradejs/strategy-kit": "3.0.4",
45
- "@tradejs/types": "^3.1.27-beta.247",
45
+ "@tradejs/types": "^3.1.27-beta.249",
46
46
  "args": "^5.0.3",
47
47
  "bcryptjs": "^2.4.3",
48
48
  "chalk": "4.1.2",