@tradejs/cli 3.0.0 → 3.1.0

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.
@@ -50,11 +50,235 @@ module.exports = __toCommonJS(strategyRelease_exports);
50
50
  var import_node_crypto = require("crypto");
51
51
  var import_promises = __toESM(require("fs/promises"));
52
52
  var import_node_path = __toESM(require("path"));
53
- var import_types = require("@tradejs/types");
53
+ var import_types2 = require("@tradejs/types");
54
54
  var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
55
+
56
+ // src/lib/strategyRelease/liveDiagnosis.ts
57
+ var import_types = require("@tradejs/types");
58
+ var buildStrategyLiveDiagnosis = (input) => {
59
+ const minimumClosedTrades = input.minimumClosedTrades ?? 20;
60
+ const minimumParityRatio = input.minimumParityRatio ?? 0.95;
61
+ const maximumOrderFailureRate = input.maximumOrderFailureRate ?? 0.05;
62
+ const minimumRegimeCoverage = input.minimumRegimeCoverage ?? 0.5;
63
+ const hasExplicitRiskScale = input.riskScaleComparable != null;
64
+ const comparisonObservedDrawdown = hasExplicitRiskScale ? input.riskScaleComparable ? input.normalizedObservedDrawdown ?? null : null : input.observedDrawdown;
65
+ const runtimeDivergence = !input.lineageComparable || input.parityRatio != null && input.parityRatio < minimumParityRatio || input.orderFailureRate != null && input.orderFailureRate > maximumOrderFailureRate;
66
+ const insufficient = input.closedTrades < minimumClosedTrades || input.parityRatio == null || comparisonObservedDrawdown == null || input.historicalDrawdownP95 == null;
67
+ const expected = !runtimeDivergence && !insufficient && comparisonObservedDrawdown <= input.historicalDrawdownP95;
68
+ const verdict = runtimeDivergence ? "RUNTIME_DIVERGENCE" : insufficient ? "INSUFFICIENT_EVIDENCE" : expected ? "EXPECTED_DRAWDOWN" : "GENERALIZATION_FAILURE";
69
+ const attributionIncomplete = input.rawCoreExpectancyDelta == null || input.aiGateAddedValue == null || input.regimeCoverage == null;
70
+ const attributedVerdict = verdict === "GENERALIZATION_FAILURE" && attributionIncomplete ? "INSUFFICIENT_EVIDENCE" : verdict;
71
+ const subtype = attributedVerdict !== "GENERALIZATION_FAILURE" ? null : input.rawCoreExpectancyDelta < 0 && input.aiGateAddedValue >= 0 ? "RAW_CORE_DECAY" : input.aiGateAddedValue < 0 ? "AI_GATE_FAILURE" : input.regimeCoverage < minimumRegimeCoverage ? "REGIME_SHIFT" : (input.overfitProbability ?? 0) >= 0.5 ? "SUSPECTED_HISTORICAL_OVERFIT" : "RAW_CORE_DECAY";
72
+ const explanation = attributedVerdict === "RUNTIME_DIVERGENCE" ? "Runtime cannot be compared economically until lineage, replay parity, and order execution agree with the frozen composition." : attributedVerdict === "EXPECTED_DRAWDOWN" ? "The observed drawdown is within the preregistered equal-length historical drawdown envelope." : attributedVerdict === "GENERALIZATION_FAILURE" ? `Runtime is comparable, but the observed drawdown exceeds the historical envelope (${subtype}).` : attributionIncomplete && verdict === "GENERALIZATION_FAILURE" ? "The drawdown breaches its historical envelope, but matched shadow raw-core, deterministic-gate, or causal regime evidence is missing for attribution." : "There are not enough comparable closed trades and historical drawdown observations for attribution.";
73
+ const recommendations = attributedVerdict === "RUNTIME_DIVERGENCE" ? [
74
+ "Resolve lineage and replay parity mismatches before evaluating strategy economics.",
75
+ "Inspect order failures and execution residuals against the frozen execution model."
76
+ ] : attributedVerdict === "EXPECTED_DRAWDOWN" ? [
77
+ "Keep the frozen composition unchanged and continue prospective evidence collection."
78
+ ] : attributedVerdict === "GENERALIZATION_FAILURE" ? [
79
+ "Do not retune the live cohort; open a new immutable research lineage.",
80
+ "Compare raw-core decay, AI-gate added value, and causal regime coverage."
81
+ ] : [
82
+ attributionIncomplete && verdict === "GENERALIZATION_FAILURE" ? "Collect matched shadow raw-core, deterministic-gate, and causal regime evidence without changing the composition." : "Collect more verified prospective outcomes without changing the composition."
83
+ ];
84
+ return {
85
+ schema: import_types.STRATEGY_LIVE_DIAGNOSIS_SCHEMA,
86
+ strategy: input.strategy,
87
+ compositionId: input.compositionId,
88
+ createdAt: input.createdAt,
89
+ verdict: attributedVerdict,
90
+ subtype,
91
+ confidence: runtimeDivergence || input.closedTrades >= 50 ? "high" : input.closedTrades >= 20 ? "medium" : "low",
92
+ evidence: {
93
+ lineageComparable: input.lineageComparable,
94
+ riskScaleComparable: input.riskScaleComparable ?? false,
95
+ releaseMaxLossValue: input.releaseMaxLossValue ?? null,
96
+ runtimeMaxLossValue: input.runtimeMaxLossValue ?? null,
97
+ riskScaleRatio: input.riskScaleRatio ?? null,
98
+ parityRatio: input.parityRatio,
99
+ orderFailureRate: input.orderFailureRate,
100
+ observedDrawdown: input.observedDrawdown,
101
+ normalizedObservedDrawdown: input.normalizedObservedDrawdown ?? null,
102
+ historicalDrawdownP95: input.historicalDrawdownP95,
103
+ historicalDrawdownMaximum: input.historicalDrawdownMaximum,
104
+ closedTrades: input.closedTrades,
105
+ rawCoreExpectancyDelta: input.rawCoreExpectancyDelta,
106
+ aiGateAddedValue: input.aiGateAddedValue,
107
+ regimeCoverage: input.regimeCoverage,
108
+ overfitProbability: input.overfitProbability
109
+ },
110
+ explanation,
111
+ recommendations
112
+ };
113
+ };
114
+ var buildStrategyLiveDiagnosisFromScorecard = ({
115
+ manifest,
116
+ scorecard,
117
+ days
118
+ }) => {
119
+ const rolling = scorecard.rolling.find((entry) => entry.days === days);
120
+ const envelope = manifest.monitoring.drawdownEnvelopes.find(
121
+ (entry) => entry.days === days
122
+ );
123
+ const orderFailureRate = scorecard.funnel.orderAttempts ? scorecard.funnel.orderFailures / scorecard.funnel.orderAttempts : null;
124
+ const lineage = scorecard.lineage;
125
+ const lineageComparable = scorecard.parity.lineageReason == null && lineage?.complete === true && lineage.conflicts === false && lineage.compositionId === manifest.composition.compositionId && lineage.gitDirty === false && lineage.gitSha === manifest.composition.gitSha && lineage.configFingerprint === manifest.composition.runtimeConfigFingerprint && lineage.gateFingerprint === manifest.composition.gateFingerprint && lineage.contextFingerprint === manifest.composition.runtimeContextFingerprint;
126
+ const releaseMaxLossValue = manifest.composition.maxLossValue;
127
+ const runtimeMaxLossValue = lineage?.maxLossValue ?? null;
128
+ const riskScaleComparable = Number.isFinite(releaseMaxLossValue) && releaseMaxLossValue > 0 && runtimeMaxLossValue != null && Number.isFinite(runtimeMaxLossValue) && runtimeMaxLossValue > 0;
129
+ const riskScaleRatio = riskScaleComparable ? runtimeMaxLossValue / releaseMaxLossValue : null;
130
+ const normalizedObservedDrawdown = rolling != null && riskScaleRatio != null ? rolling.maxDrawdown / riskScaleRatio : null;
131
+ const normalizeRuntimeEconomics = (value) => value != null && riskScaleRatio != null ? value / riskScaleRatio : null;
132
+ return buildStrategyLiveDiagnosis({
133
+ strategy: manifest.strategy,
134
+ compositionId: manifest.composition.compositionId,
135
+ createdAt: scorecard.generatedAt,
136
+ lineageComparable,
137
+ riskScaleComparable,
138
+ releaseMaxLossValue,
139
+ runtimeMaxLossValue,
140
+ riskScaleRatio,
141
+ parityRatio: scorecard.parity.ratio,
142
+ orderFailureRate,
143
+ observedDrawdown: rolling?.maxDrawdown ?? null,
144
+ normalizedObservedDrawdown,
145
+ historicalDrawdownP95: envelope?.p95 ?? null,
146
+ historicalDrawdownMaximum: envelope?.maximum ?? null,
147
+ closedTrades: rolling?.closedTrades ?? 0,
148
+ rawCoreExpectancyDelta: scorecard.prospective?.rawCoreExpectancy != null && manifest.monitoring.rawCoreExpectancy != null ? normalizeRuntimeEconomics(scorecard.prospective.rawCoreExpectancy) - manifest.monitoring.rawCoreExpectancy : null,
149
+ aiGateAddedValue: scorecard.prospective?.rawCoreExpectancy != null && scorecard.prospective.aiGateExpectancy != null ? normalizeRuntimeEconomics(scorecard.prospective.aiGateExpectancy) - normalizeRuntimeEconomics(scorecard.prospective.rawCoreExpectancy) : null,
150
+ regimeCoverage: scorecard.prospective?.regimeCoverage ?? null,
151
+ overfitProbability: manifest.monitoring.overfitProbability,
152
+ minimumClosedTrades: manifest.monitoring.minimumProspectiveClosedTrades,
153
+ minimumParityRatio: manifest.monitoring.minimumParityRatio,
154
+ maximumOrderFailureRate: manifest.monitoring.maximumOrderFailureRate,
155
+ minimumRegimeCoverage: manifest.monitoring.minimumRegimeCoverage
156
+ });
157
+ };
158
+
159
+ // src/lib/strategyRelease/monitoringProfile.ts
160
+ var DAY_MS = 864e5;
161
+ function assert(condition, message) {
162
+ if (!condition) throw new Error(`Invalid strategy release: ${message}`);
163
+ }
164
+ var realizedDrawdown = (trades, startIndex = 0, endIndex = trades.length) => {
165
+ let equity = 0;
166
+ let peak = 0;
167
+ let maximum = 0;
168
+ for (let index = startIndex; index < endIndex; index += 1) {
169
+ const trade = trades[index];
170
+ equity += trade.netProfit;
171
+ peak = Math.max(peak, equity);
172
+ maximum = Math.max(maximum, peak - equity);
173
+ }
174
+ return maximum;
175
+ };
176
+ var lowerBoundExitTimestamp = (trades, timestamp) => {
177
+ let left = 0;
178
+ let right = trades.length;
179
+ while (left < right) {
180
+ const middle = Math.floor((left + right) / 2);
181
+ if (trades[middle].exitTimestamp < timestamp) left = middle + 1;
182
+ else right = middle;
183
+ }
184
+ return left;
185
+ };
186
+ var percentile = (values, probability) => {
187
+ const sorted = [...values].sort((left, right) => left - right);
188
+ const position = (sorted.length - 1) * probability;
189
+ const lower = Math.floor(position);
190
+ const upper = Math.ceil(position);
191
+ if (lower === upper) return sorted[lower];
192
+ return sorted[lower] * (upper - position) + sorted[upper] * (position - lower);
193
+ };
194
+ var buildStrategyMonitoringProfile = ({
195
+ trades,
196
+ startTime,
197
+ endTime,
198
+ days,
199
+ minimumProspectiveClosedTrades = 20,
200
+ minimumParityRatio = 0.95,
201
+ maximumOrderFailureRate = 0.05,
202
+ minimumRegimeCoverage = 0.5,
203
+ aiGateExpectancy = null,
204
+ overfitProbability = null
205
+ }) => {
206
+ assert(endTime > startTime, "monitoring profile window is invalid");
207
+ assert(days.length > 0, "monitoring profile days are required");
208
+ const ordered = trades.filter(
209
+ (trade) => Number.isFinite(trade.exitTimestamp) && Number.isFinite(trade.netProfit) && trade.exitTimestamp >= startTime && trade.exitTimestamp < endTime
210
+ ).sort((left, right) => left.exitTimestamp - right.exitTimestamp);
211
+ const drawdownEnvelopes = [...new Set(days)].sort((left, right) => left - right).map((windowDays) => {
212
+ assert(
213
+ Number.isInteger(windowDays) && windowDays > 0,
214
+ "monitoring profile days must be positive integers"
215
+ );
216
+ const duration = windowDays * DAY_MS;
217
+ assert(
218
+ endTime - startTime >= duration,
219
+ `${windowDays}d monitoring window exceeds historical coverage`
220
+ );
221
+ const drawdowns = [];
222
+ for (let windowStart = startTime; windowStart + duration <= endTime; windowStart += DAY_MS) {
223
+ const windowEnd = windowStart + duration;
224
+ drawdowns.push(
225
+ realizedDrawdown(
226
+ ordered,
227
+ lowerBoundExitTimestamp(ordered, windowStart),
228
+ lowerBoundExitTimestamp(ordered, windowEnd)
229
+ )
230
+ );
231
+ }
232
+ return {
233
+ days: windowDays,
234
+ p95: percentile(drawdowns, 0.95),
235
+ maximum: Math.max(...drawdowns)
236
+ };
237
+ });
238
+ return {
239
+ minimumProspectiveClosedTrades,
240
+ minimumParityRatio,
241
+ maximumOrderFailureRate,
242
+ minimumRegimeCoverage,
243
+ drawdownEnvelopes,
244
+ rawCoreExpectancy: ordered.length ? ordered.reduce((sum, trade) => sum + trade.netProfit, 0) / ordered.length : null,
245
+ aiGateExpectancy,
246
+ overfitProbability
247
+ };
248
+ };
249
+
250
+ // src/lib/strategyRelease/evidenceRetention.ts
251
+ var DAY_MS2 = 864e5;
252
+ var planStrategyEvidenceRetention = ({
253
+ now,
254
+ entries,
255
+ retentionDays = {
256
+ operational_redis: 3,
257
+ verbose_payload: 14,
258
+ verified_runtime_bundle: 90,
259
+ compact_ledger: null
260
+ }
261
+ }) => {
262
+ const keep = [];
263
+ const remove = [];
264
+ for (const entry of entries) {
265
+ const days = retentionDays[entry.kind];
266
+ if (days == null || !entry.verified || !entry.aggregated || now - entry.createdAt <= days * DAY_MS2) {
267
+ keep.push(entry);
268
+ } else {
269
+ remove.push(entry);
270
+ }
271
+ }
272
+ return {
273
+ keep,
274
+ delete: remove,
275
+ bytesReclaimable: remove.reduce((total, entry) => total + entry.bytes, 0)
276
+ };
277
+ };
278
+
279
+ // src/lib/strategyRelease.ts
55
280
  var SHA256_RE = /^[a-f0-9]{64}$/;
56
281
  var LINEAGE_FINGERPRINT_RE = /^[a-f0-9]{16}$/;
57
- var DAY_MS = 864e5;
58
282
  var REQUIRED_LONG_WINDOWS = [1095, 1460, 1825];
59
283
  var REQUIRED_STABLE_TERMINAL_WINDOWS = [365, 180, 90];
60
284
  var MIN_RECENT_DIRECTION_REPAIR_TRADES = 20;
@@ -193,11 +417,11 @@ var deriveStrategyReleaseResearchDecision = async (input) => {
193
417
  };
194
418
  var canonicalStrategyReleaseJson = (value) => (0, import_strategyReleaseEvidence.canonicalStrategyEvidenceJson)(value);
195
419
  var strategyReleaseSha256 = (value) => (0, import_strategyReleaseEvidence.strategyEvidenceSha256)(value);
196
- function assert(condition, message) {
420
+ function assert2(condition, message) {
197
421
  if (!condition) throw new Error(`Invalid strategy release: ${message}`);
198
422
  }
199
- var assertFingerprint = (value, label) => assert(SHA256_RE.test(value), `${label} must be a lowercase SHA-256`);
200
- var assertLineageFingerprint = (value, label) => assert(
423
+ var assertFingerprint = (value, label) => assert2(SHA256_RE.test(value), `${label} must be a lowercase SHA-256`);
424
+ var assertLineageFingerprint = (value, label) => assert2(
201
425
  LINEAGE_FINGERPRINT_RE.test(value),
202
426
  `${label} must be a 16-character lowercase lineage fingerprint`
203
427
  );
@@ -245,7 +469,7 @@ var deriveReleaseGates = (evidence) => {
245
469
  };
246
470
  var verifyEvidenceComposition = (strategy, composition, evidence) => {
247
471
  const directionPolicy = composition.directionPolicy ?? "both";
248
- const requireEqual = (actual, expected, label, artifactId) => assert(
472
+ const requireEqual = (actual, expected, label, artifactId) => assert2(
249
473
  actual === expected,
250
474
  `evidence ${artifactId} ${label} does not match the frozen composition`
251
475
  );
@@ -277,7 +501,7 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
277
501
  "MAX_LOSS_VALUE",
278
502
  reference.artifactId
279
503
  );
280
- assert(
504
+ assert2(
281
505
  lineage?.sourceSha256s.includes(composition.coreExportSha256),
282
506
  `evidence ${reference.artifactId} does not include the frozen core export`
283
507
  );
@@ -323,7 +547,7 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
323
547
  reference.artifactId
324
548
  );
325
549
  }
326
- assert(
550
+ assert2(
327
551
  lineage?.sourceSha256s.includes(composition.coreExportSha256),
328
552
  `evidence ${reference.artifactId} does not use the frozen core export`
329
553
  );
@@ -427,60 +651,60 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
427
651
  }
428
652
  };
429
653
  var createStrategyReleaseManifest = (input) => {
430
- assert(input.strategy.trim(), "strategy is required");
431
- assert(
654
+ assert2(input.strategy.trim(), "strategy is required");
655
+ assert2(
432
656
  Number.isFinite(input.createdAt),
433
657
  "createdAt must be a finite timestamp"
434
658
  );
435
- assert(
659
+ assert2(
436
660
  input.researchBudget.hypothesisFamilies <= 3,
437
661
  "at most 3 hypothesis families are allowed"
438
662
  );
439
- assert(
663
+ assert2(
440
664
  input.researchBudget.maximumVariantsPerFamily <= 5,
441
665
  "at most 5 variants per family are allowed"
442
666
  );
443
- assert(
667
+ assert2(
444
668
  input.researchBudget.isolatedLongFinalists <= 1,
445
669
  "at most one isolated-long finalist is allowed"
446
670
  );
447
- assert(
671
+ assert2(
448
672
  input.researchBudget.aiGateTuningRounds <= 1,
449
673
  "at most one AI-gate tuning round is allowed"
450
674
  );
451
- assert(input.marketWindow.cacheOnly, "market window must be cache-only");
452
- assert(
675
+ assert2(input.marketWindow.cacheOnly, "market window must be cache-only");
676
+ assert2(
453
677
  input.marketWindow.endTime > input.marketWindow.startTime,
454
678
  "market window is invalid"
455
679
  );
456
- assert(input.composition.maxLossValue > 0, "MAX_LOSS_VALUE must be positive");
457
- assert(
680
+ assert2(input.composition.maxLossValue > 0, "MAX_LOSS_VALUE must be positive");
681
+ assert2(
458
682
  input.composition.longEnabled && input.composition.shortEnabled,
459
683
  "LONG and SHORT must both remain enabled"
460
684
  );
461
- assert(
685
+ assert2(
462
686
  input.composition.directionPolicy == null || ["both", "long_only", "short_only", "direction_aware"].includes(
463
687
  input.composition.directionPolicy
464
688
  ),
465
689
  "composition.directionPolicy is invalid"
466
690
  );
467
- assert(
691
+ assert2(
468
692
  Number.isInteger(input.monitoring.minimumProspectiveClosedTrades) && input.monitoring.minimumProspectiveClosedTrades > 0,
469
693
  "monitoring.minimumProspectiveClosedTrades must be positive"
470
694
  );
471
- assert(
695
+ assert2(
472
696
  Number.isFinite(input.monitoring.minimumParityRatio) && input.monitoring.minimumParityRatio >= 0 && input.monitoring.minimumParityRatio <= 1,
473
697
  "monitoring.minimumParityRatio must be between 0 and 1"
474
698
  );
475
- assert(
699
+ assert2(
476
700
  Number.isFinite(input.monitoring.maximumOrderFailureRate) && input.monitoring.maximumOrderFailureRate >= 0 && input.monitoring.maximumOrderFailureRate <= 1,
477
701
  "monitoring.maximumOrderFailureRate must be between 0 and 1"
478
702
  );
479
- assert(
703
+ assert2(
480
704
  Number.isFinite(input.monitoring.minimumRegimeCoverage) && input.monitoring.minimumRegimeCoverage >= 0 && input.monitoring.minimumRegimeCoverage <= 1,
481
705
  "monitoring.minimumRegimeCoverage must be between 0 and 1"
482
706
  );
483
- assert(
707
+ assert2(
484
708
  input.monitoring.drawdownEnvelopes.length > 0 && input.monitoring.drawdownEnvelopes.every(
485
709
  (entry) => Number.isInteger(entry.days) && entry.days > 0 && Number.isFinite(entry.p95) && Number.isFinite(entry.maximum) && entry.p95 >= 0 && entry.maximum >= entry.p95
486
710
  ),
@@ -514,7 +738,7 @@ var createStrategyReleaseManifest = (input) => {
514
738
  }
515
739
  verifyEvidenceComposition(input.strategy, input.composition, input.evidence);
516
740
  const evidenceGates = deriveReleaseGates(input.evidence);
517
- assert(
741
+ assert2(
518
742
  canonicalStrategyReleaseJson(input.gates) === canonicalStrategyReleaseJson(evidenceGates),
519
743
  "release gates must be derived from verified evidence assertions"
520
744
  );
@@ -561,7 +785,7 @@ var createStrategyReleaseManifest = (input) => {
561
785
  verdict
562
786
  };
563
787
  return {
564
- schema: import_types.STRATEGY_RELEASE_SCHEMA,
788
+ schema: import_types2.STRATEGY_RELEASE_SCHEMA,
565
789
  releaseId: `${safeSegment(input.strategy, "strategy")}_${compactTimestamp(input.createdAt)}_${strategyReleaseSha256(releaseIdentity).slice(0, 16)}`,
566
790
  strategy: input.strategy,
567
791
  createdAt: input.createdAt,
@@ -588,21 +812,21 @@ var createStrategyReleaseEnvelope = (manifest) => ({
588
812
  var verifyStrategyReleaseEnvelope = async (valueOrPath) => {
589
813
  const value = typeof valueOrPath === "string" ? JSON.parse(await import_promises.default.readFile(valueOrPath, "utf8")) : valueOrPath;
590
814
  const envelope = value;
591
- assert(
815
+ assert2(
592
816
  envelope?.schema === "tradejs-strategy-release-envelope/v1",
593
817
  "release envelope schema mismatch"
594
818
  );
595
- assert(
819
+ assert2(
596
820
  envelope.releaseId === envelope.manifest?.releaseId,
597
821
  "releaseId mismatch"
598
822
  );
599
- assert(
823
+ assert2(
600
824
  strategyReleaseSha256(envelope.manifest) === envelope.manifestSha256,
601
825
  "release manifest checksum mismatch"
602
826
  );
603
827
  const manifest = envelope.manifest;
604
- assert(
605
- manifest.schema === import_types.STRATEGY_RELEASE_SCHEMA,
828
+ assert2(
829
+ manifest.schema === import_types2.STRATEGY_RELEASE_SCHEMA,
606
830
  "manifest schema mismatch"
607
831
  );
608
832
  const rebuilt = createStrategyReleaseManifest({
@@ -630,7 +854,7 @@ var verifyStrategyReleaseEnvelope = async (valueOrPath) => {
630
854
  summary: manifest.verdict.summary,
631
855
  prospective: manifest.prospective
632
856
  });
633
- assert(
857
+ assert2(
634
858
  canonicalStrategyReleaseJson(rebuilt) === canonicalStrategyReleaseJson(manifest),
635
859
  "release manifest contains self-declared or inconsistent derived fields"
636
860
  );
@@ -745,191 +969,6 @@ var publishStrategyRelease = async ({
745
969
  ]);
746
970
  return { releasePath, markerPath, envelope, markerEnvelope };
747
971
  };
748
- var buildStrategyLiveDiagnosis = (input) => {
749
- const minimumClosedTrades = input.minimumClosedTrades ?? 20;
750
- const minimumParityRatio = input.minimumParityRatio ?? 0.95;
751
- const maximumOrderFailureRate = input.maximumOrderFailureRate ?? 0.05;
752
- const minimumRegimeCoverage = input.minimumRegimeCoverage ?? 0.5;
753
- const hasExplicitRiskScale = input.riskScaleComparable != null;
754
- const comparisonObservedDrawdown = hasExplicitRiskScale ? input.riskScaleComparable ? input.normalizedObservedDrawdown ?? null : null : input.observedDrawdown;
755
- const runtimeDivergence = !input.lineageComparable || input.parityRatio != null && input.parityRatio < minimumParityRatio || input.orderFailureRate != null && input.orderFailureRate > maximumOrderFailureRate;
756
- const insufficient = input.closedTrades < minimumClosedTrades || input.parityRatio == null || comparisonObservedDrawdown == null || input.historicalDrawdownP95 == null;
757
- const expected = !runtimeDivergence && !insufficient && comparisonObservedDrawdown <= input.historicalDrawdownP95;
758
- const verdict = runtimeDivergence ? "RUNTIME_DIVERGENCE" : insufficient ? "INSUFFICIENT_EVIDENCE" : expected ? "EXPECTED_DRAWDOWN" : "GENERALIZATION_FAILURE";
759
- const attributionIncomplete = input.rawCoreExpectancyDelta == null || input.aiGateAddedValue == null || input.regimeCoverage == null;
760
- const attributedVerdict = verdict === "GENERALIZATION_FAILURE" && attributionIncomplete ? "INSUFFICIENT_EVIDENCE" : verdict;
761
- const subtype = attributedVerdict !== "GENERALIZATION_FAILURE" ? null : input.rawCoreExpectancyDelta < 0 && input.aiGateAddedValue >= 0 ? "RAW_CORE_DECAY" : input.aiGateAddedValue < 0 ? "AI_GATE_FAILURE" : input.regimeCoverage < minimumRegimeCoverage ? "REGIME_SHIFT" : (input.overfitProbability ?? 0) >= 0.5 ? "SUSPECTED_HISTORICAL_OVERFIT" : "RAW_CORE_DECAY";
762
- const explanation = attributedVerdict === "RUNTIME_DIVERGENCE" ? "Runtime cannot be compared economically until lineage, replay parity, and order execution agree with the frozen composition." : attributedVerdict === "EXPECTED_DRAWDOWN" ? "The observed drawdown is within the preregistered equal-length historical drawdown envelope." : attributedVerdict === "GENERALIZATION_FAILURE" ? `Runtime is comparable, but the observed drawdown exceeds the historical envelope (${subtype}).` : attributionIncomplete && verdict === "GENERALIZATION_FAILURE" ? "The drawdown breaches its historical envelope, but matched shadow raw-core, deterministic-gate, or causal regime evidence is missing for attribution." : "There are not enough comparable closed trades and historical drawdown observations for attribution.";
763
- const recommendations = attributedVerdict === "RUNTIME_DIVERGENCE" ? [
764
- "Resolve lineage and replay parity mismatches before evaluating strategy economics.",
765
- "Inspect order failures and execution residuals against the frozen execution model."
766
- ] : attributedVerdict === "EXPECTED_DRAWDOWN" ? [
767
- "Keep the frozen composition unchanged and continue prospective evidence collection."
768
- ] : attributedVerdict === "GENERALIZATION_FAILURE" ? [
769
- "Do not retune the live cohort; open a new immutable research lineage.",
770
- "Compare raw-core decay, AI-gate added value, and causal regime coverage."
771
- ] : [
772
- attributionIncomplete && verdict === "GENERALIZATION_FAILURE" ? "Collect matched shadow raw-core, deterministic-gate, and causal regime evidence without changing the composition." : "Collect more verified prospective outcomes without changing the composition."
773
- ];
774
- return {
775
- schema: import_types.STRATEGY_LIVE_DIAGNOSIS_SCHEMA,
776
- strategy: input.strategy,
777
- compositionId: input.compositionId,
778
- createdAt: input.createdAt,
779
- verdict: attributedVerdict,
780
- subtype,
781
- confidence: runtimeDivergence || input.closedTrades >= 50 ? "high" : input.closedTrades >= 20 ? "medium" : "low",
782
- evidence: {
783
- lineageComparable: input.lineageComparable,
784
- riskScaleComparable: input.riskScaleComparable ?? false,
785
- releaseMaxLossValue: input.releaseMaxLossValue ?? null,
786
- runtimeMaxLossValue: input.runtimeMaxLossValue ?? null,
787
- riskScaleRatio: input.riskScaleRatio ?? null,
788
- parityRatio: input.parityRatio,
789
- orderFailureRate: input.orderFailureRate,
790
- observedDrawdown: input.observedDrawdown,
791
- normalizedObservedDrawdown: input.normalizedObservedDrawdown ?? null,
792
- historicalDrawdownP95: input.historicalDrawdownP95,
793
- historicalDrawdownMaximum: input.historicalDrawdownMaximum,
794
- closedTrades: input.closedTrades,
795
- rawCoreExpectancyDelta: input.rawCoreExpectancyDelta,
796
- aiGateAddedValue: input.aiGateAddedValue,
797
- regimeCoverage: input.regimeCoverage,
798
- overfitProbability: input.overfitProbability
799
- },
800
- explanation,
801
- recommendations
802
- };
803
- };
804
- var buildStrategyLiveDiagnosisFromScorecard = ({
805
- manifest,
806
- scorecard,
807
- days
808
- }) => {
809
- const rolling = scorecard.rolling.find((entry) => entry.days === days);
810
- const envelope = manifest.monitoring.drawdownEnvelopes.find(
811
- (entry) => entry.days === days
812
- );
813
- const orderFailureRate = scorecard.funnel.orderAttempts ? scorecard.funnel.orderFailures / scorecard.funnel.orderAttempts : null;
814
- const lineage = scorecard.lineage;
815
- const lineageComparable = scorecard.parity.lineageReason == null && lineage?.complete === true && lineage.conflicts === false && lineage.compositionId === manifest.composition.compositionId && lineage.gitDirty === false && lineage.gitSha === manifest.composition.gitSha && lineage.configFingerprint === manifest.composition.runtimeConfigFingerprint && lineage.gateFingerprint === manifest.composition.gateFingerprint && lineage.contextFingerprint === manifest.composition.runtimeContextFingerprint;
816
- const releaseMaxLossValue = manifest.composition.maxLossValue;
817
- const runtimeMaxLossValue = lineage?.maxLossValue ?? null;
818
- const riskScaleComparable = Number.isFinite(releaseMaxLossValue) && releaseMaxLossValue > 0 && runtimeMaxLossValue != null && Number.isFinite(runtimeMaxLossValue) && runtimeMaxLossValue > 0;
819
- const riskScaleRatio = riskScaleComparable ? runtimeMaxLossValue / releaseMaxLossValue : null;
820
- const normalizedObservedDrawdown = rolling != null && riskScaleRatio != null ? rolling.maxDrawdown / riskScaleRatio : null;
821
- const normalizeRuntimeEconomics = (value) => value != null && riskScaleRatio != null ? value / riskScaleRatio : null;
822
- return buildStrategyLiveDiagnosis({
823
- strategy: manifest.strategy,
824
- compositionId: manifest.composition.compositionId,
825
- createdAt: scorecard.generatedAt,
826
- lineageComparable,
827
- riskScaleComparable,
828
- releaseMaxLossValue,
829
- runtimeMaxLossValue,
830
- riskScaleRatio,
831
- parityRatio: scorecard.parity.ratio,
832
- orderFailureRate,
833
- observedDrawdown: rolling?.maxDrawdown ?? null,
834
- normalizedObservedDrawdown,
835
- historicalDrawdownP95: envelope?.p95 ?? null,
836
- historicalDrawdownMaximum: envelope?.maximum ?? null,
837
- closedTrades: rolling?.closedTrades ?? 0,
838
- rawCoreExpectancyDelta: scorecard.prospective?.rawCoreExpectancy != null && manifest.monitoring.rawCoreExpectancy != null ? normalizeRuntimeEconomics(scorecard.prospective.rawCoreExpectancy) - manifest.monitoring.rawCoreExpectancy : null,
839
- aiGateAddedValue: scorecard.prospective?.rawCoreExpectancy != null && scorecard.prospective.aiGateExpectancy != null ? normalizeRuntimeEconomics(scorecard.prospective.aiGateExpectancy) - normalizeRuntimeEconomics(scorecard.prospective.rawCoreExpectancy) : null,
840
- regimeCoverage: scorecard.prospective?.regimeCoverage ?? null,
841
- overfitProbability: manifest.monitoring.overfitProbability,
842
- minimumClosedTrades: manifest.monitoring.minimumProspectiveClosedTrades,
843
- minimumParityRatio: manifest.monitoring.minimumParityRatio,
844
- maximumOrderFailureRate: manifest.monitoring.maximumOrderFailureRate,
845
- minimumRegimeCoverage: manifest.monitoring.minimumRegimeCoverage
846
- });
847
- };
848
- var realizedDrawdown = (trades, startIndex = 0, endIndex = trades.length) => {
849
- let equity = 0;
850
- let peak = 0;
851
- let maximum = 0;
852
- for (let index = startIndex; index < endIndex; index += 1) {
853
- const trade = trades[index];
854
- equity += trade.netProfit;
855
- peak = Math.max(peak, equity);
856
- maximum = Math.max(maximum, peak - equity);
857
- }
858
- return maximum;
859
- };
860
- var lowerBoundExitTimestamp = (trades, timestamp) => {
861
- let left = 0;
862
- let right = trades.length;
863
- while (left < right) {
864
- const middle = Math.floor((left + right) / 2);
865
- if (trades[middle].exitTimestamp < timestamp) left = middle + 1;
866
- else right = middle;
867
- }
868
- return left;
869
- };
870
- var percentile = (values, probability) => {
871
- const sorted = [...values].sort((left, right) => left - right);
872
- const position = (sorted.length - 1) * probability;
873
- const lower = Math.floor(position);
874
- const upper = Math.ceil(position);
875
- if (lower === upper) return sorted[lower];
876
- return sorted[lower] * (upper - position) + sorted[upper] * (position - lower);
877
- };
878
- var buildStrategyMonitoringProfile = ({
879
- trades,
880
- startTime,
881
- endTime,
882
- days,
883
- minimumProspectiveClosedTrades = 20,
884
- minimumParityRatio = 0.95,
885
- maximumOrderFailureRate = 0.05,
886
- minimumRegimeCoverage = 0.5,
887
- aiGateExpectancy = null,
888
- overfitProbability = null
889
- }) => {
890
- assert(endTime > startTime, "monitoring profile window is invalid");
891
- assert(days.length > 0, "monitoring profile days are required");
892
- const ordered = trades.filter(
893
- (trade) => Number.isFinite(trade.exitTimestamp) && Number.isFinite(trade.netProfit) && trade.exitTimestamp >= startTime && trade.exitTimestamp < endTime
894
- ).sort((left, right) => left.exitTimestamp - right.exitTimestamp);
895
- const drawdownEnvelopes = [...new Set(days)].sort((left, right) => left - right).map((windowDays) => {
896
- assert(
897
- Number.isInteger(windowDays) && windowDays > 0,
898
- "monitoring profile days must be positive integers"
899
- );
900
- const duration = windowDays * DAY_MS;
901
- assert(
902
- endTime - startTime >= duration,
903
- `${windowDays}d monitoring window exceeds historical coverage`
904
- );
905
- const drawdowns = [];
906
- for (let windowStart = startTime; windowStart + duration <= endTime; windowStart += DAY_MS) {
907
- const windowEnd = windowStart + duration;
908
- drawdowns.push(
909
- realizedDrawdown(
910
- ordered,
911
- lowerBoundExitTimestamp(ordered, windowStart),
912
- lowerBoundExitTimestamp(ordered, windowEnd)
913
- )
914
- );
915
- }
916
- return {
917
- days: windowDays,
918
- p95: percentile(drawdowns, 0.95),
919
- maximum: Math.max(...drawdowns)
920
- };
921
- });
922
- return {
923
- minimumProspectiveClosedTrades,
924
- minimumParityRatio,
925
- maximumOrderFailureRate,
926
- minimumRegimeCoverage,
927
- drawdownEnvelopes,
928
- rawCoreExpectancy: ordered.length ? ordered.reduce((sum, trade) => sum + trade.netProfit, 0) / ordered.length : null,
929
- aiGateExpectancy,
930
- overfitProbability
931
- };
932
- };
933
972
  var publishStrategyLiveDiagnosis = async ({
934
973
  rootDir,
935
974
  diagnosis,
@@ -1013,32 +1052,6 @@ var publishStrategyLiveDiagnosis = async ({
1013
1052
  await verifyStrategyEvidenceMarkerEnvelope(markerPath);
1014
1053
  return { diagnosisPath, markerPath, envelope, markerEnvelope };
1015
1054
  };
1016
- var planStrategyEvidenceRetention = ({
1017
- now,
1018
- entries,
1019
- retentionDays = {
1020
- operational_redis: 3,
1021
- verbose_payload: 14,
1022
- verified_runtime_bundle: 90,
1023
- compact_ledger: null
1024
- }
1025
- }) => {
1026
- const keep = [];
1027
- const remove = [];
1028
- for (const entry of entries) {
1029
- const days = retentionDays[entry.kind];
1030
- if (days == null || !entry.verified || !entry.aggregated || now - entry.createdAt <= days * DAY_MS) {
1031
- keep.push(entry);
1032
- } else {
1033
- remove.push(entry);
1034
- }
1035
- }
1036
- return {
1037
- keep,
1038
- delete: remove,
1039
- bytesReclaimable: remove.reduce((total, entry) => total + entry.bytes, 0)
1040
- };
1041
- };
1042
1055
  var finiteString = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
1043
1056
  var finiteNumber = (value) => {
1044
1057
  const number = Number(value);
@@ -1214,39 +1227,39 @@ var verifyCompletedCoreResearchBundle = async ({
1214
1227
  }) => {
1215
1228
  const researchId = finiteString(artifact.researchId);
1216
1229
  const specSha256 = finiteString(artifact.specSha256);
1217
- assert(researchId != null, "core result researchId is required");
1218
- assert(specSha256 != null, "core result specSha256 is required");
1230
+ assert2(researchId != null, "core result researchId is required");
1231
+ assert2(specSha256 != null, "core result specSha256 is required");
1219
1232
  const researchDir = import_node_path.default.dirname(resultPath);
1220
1233
  const manifestPath = import_node_path.default.join(researchDir, "manifest.json");
1221
1234
  const specPath = import_node_path.default.join(researchDir, "spec.json");
1222
1235
  const manifestBytes = await import_promises.default.readFile(manifestPath).catch(() => null);
1223
1236
  const specBytes = await import_promises.default.readFile(specPath).catch(() => null);
1224
- assert(
1237
+ assert2(
1225
1238
  manifestBytes != null && specBytes != null,
1226
1239
  "core result must belong to a completed core research bundle"
1227
1240
  );
1228
1241
  const manifest = JSON.parse(manifestBytes.toString("utf8"));
1229
1242
  const artifactHashes = manifest.artifactHashes != null && typeof manifest.artifactHashes === "object" && !Array.isArray(manifest.artifactHashes) ? manifest.artifactHashes : null;
1230
1243
  const parsedSpec = JSON.parse(specBytes.toString("utf8"));
1231
- assert(
1244
+ assert2(
1232
1245
  manifest.schema === "tradejs-core-research-manifest/v1" && manifest.status === "completed" && manifest.researchId === researchId && manifest.specSha256 === specSha256 && artifactHashes?.["result.json"] === resultSha256 && (0, import_node_crypto.createHash)("sha256").update(canonicalStrategyReleaseJson(parsedSpec)).digest("hex") === specSha256,
1233
1246
  "core result is not bound by its completed core research manifest"
1234
1247
  );
1235
1248
  for (const [relativePath, expectedSha256] of Object.entries(
1236
1249
  artifactHashes ?? {}
1237
1250
  )) {
1238
- assert(
1251
+ assert2(
1239
1252
  !import_node_path.default.isAbsolute(relativePath) && !relativePath.split(/[\\/]/).includes(".."),
1240
1253
  `core bundle artifact ${relativePath} escapes its research directory`
1241
1254
  );
1242
- assert(
1255
+ assert2(
1243
1256
  typeof expectedSha256 === "string" && SHA256_RE.test(expectedSha256),
1244
1257
  `core bundle artifact ${relativePath} has an invalid checksum`
1245
1258
  );
1246
1259
  const actualSha256 = await (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(
1247
1260
  import_node_path.default.join(researchDir, relativePath)
1248
1261
  );
1249
- assert(
1262
+ assert2(
1250
1263
  actualSha256 === expectedSha256,
1251
1264
  `core bundle artifact ${relativePath} checksum mismatch`
1252
1265
  );
@@ -1256,7 +1269,7 @@ async function collectReleaseEvidenceReferences(references) {
1256
1269
  return Promise.all(
1257
1270
  references.map(async (reference) => {
1258
1271
  const actualSha256 = await (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(reference.path);
1259
- assert(
1272
+ assert2(
1260
1273
  actualSha256 === reference.sha256,
1261
1274
  `evidence ${reference.artifactId} checksum mismatch`
1262
1275
  );
@@ -1265,7 +1278,7 @@ async function collectReleaseEvidenceReferences(references) {
1265
1278
  const parsed = JSON.parse(
1266
1279
  await import_promises.default.readFile(reference.path, "utf8")
1267
1280
  );
1268
- assert(
1281
+ assert2(
1269
1282
  parsed != null && typeof parsed === "object" && !Array.isArray(parsed),
1270
1283
  `evidence ${reference.artifactId} must be a JSON object`
1271
1284
  );
@@ -1278,7 +1291,7 @@ async function collectReleaseEvidenceReferences(references) {
1278
1291
  const nested = (value) => value != null && typeof value === "object" && !Array.isArray(value) ? value : null;
1279
1292
  const array = (value) => Array.isArray(value) ? value : [];
1280
1293
  const semanticallyValid = reference.kind === "core_research" ? artifact.schema === "tradejs-core-research-result/v1" || artifact.schema === "tradejs-core-research-manifest/v1" && artifact.status === "completed" : reference.kind === "ai_gate" ? nested(artifact.run)?.mode === "local-deterministic" && nested(artifact.research)?.lineage != null && nested(artifact.outcome) != null : reference.kind === "runtime_parity" ? artifact.reportType === "replay-runtime-evidence" && nested(nested(artifact.replay)?.runtimeComparison) != null : reference.kind === "execution_calibration" ? artifact.reportType === "execution-calibration" && nested(artifact.summary) != null : reference.kind === "runtime_evidence" ? artifact.reportType === "runtime-evidence" : artifact.reportType === "runtime-scorecard";
1281
- assert(
1294
+ assert2(
1282
1295
  semanticallyValid,
1283
1296
  `evidence ${reference.artifactId} does not match ${reference.kind}`
1284
1297
  );