@tradejs/cli 2.0.19 → 2.0.20

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.
@@ -34,8 +34,9 @@ __export(runtimeScorecard_exports, {
34
34
  runtimeScorecard: () => runtimeScorecard
35
35
  });
36
36
  module.exports = __toCommonJS(runtimeScorecard_exports);
37
- var import_promises3 = __toESM(require("fs/promises"));
38
- var import_node_path3 = __toESM(require("path"));
37
+ var import_promises4 = __toESM(require("fs/promises"));
38
+ var import_node_crypto3 = require("crypto");
39
+ var import_node_path4 = __toESM(require("path"));
39
40
  var import_args = __toESM(require("args"));
40
41
  var import_chalk = __toESM(require("chalk"));
41
42
 
@@ -215,6 +216,71 @@ var extractRuntimeRows = (artifact) => {
215
216
  statsBuckets: asArray(runtime.evaluationStatsBuckets).map(asRecord2).filter((item) => item != null)
216
217
  };
217
218
  };
219
+ var buildRuntimeLineageSummary = (rows) => {
220
+ const lineageRows = [...rows.evaluations, ...rows.signals, ...rows.trades];
221
+ const lineages = lineageRows.map((row) => asRecord2(row.runtimeLineage)).filter((lineage) => lineage != null);
222
+ const identities = /* @__PURE__ */ new Map();
223
+ for (const lineage of lineages) {
224
+ const identity2 = {
225
+ compositionId: finiteString(lineage.compositionId),
226
+ gitSha: finiteString(lineage.gitSha),
227
+ gitDirty: typeof lineage.gitDirty === "boolean" ? lineage.gitDirty : null,
228
+ gateFingerprint: finiteString(lineage.gateFingerprint),
229
+ configFingerprint: finiteString(lineage.configFingerprint),
230
+ contextFingerprint: finiteString(lineage.contextFingerprint),
231
+ maxLossValue: finiteNumber(lineage.maxLossValue)
232
+ };
233
+ identities.set(JSON.stringify(identity2), identity2);
234
+ }
235
+ const identity = identities.size === 1 ? [...identities.values()][0] : null;
236
+ const complete = lineageRows.length > 0 && lineages.length === lineageRows.length && identity != null && identity.compositionId != null && identity.gitSha != null && identity.gitDirty === false && identity.gateFingerprint != null && identity.configFingerprint != null && identity.contextFingerprint != null && identity.maxLossValue != null;
237
+ return {
238
+ complete,
239
+ conflicts: identities.size > 1,
240
+ rows: lineageRows.length,
241
+ rowsWithLineage: lineages.length,
242
+ compositionId: finiteString(identity?.compositionId),
243
+ gitSha: finiteString(identity?.gitSha),
244
+ gitDirty: typeof identity?.gitDirty === "boolean" ? identity.gitDirty : null,
245
+ gateFingerprint: finiteString(identity?.gateFingerprint),
246
+ configFingerprint: finiteString(identity?.configFingerprint),
247
+ contextFingerprint: finiteString(identity?.contextFingerprint),
248
+ maxLossValue: finiteNumber(identity?.maxLossValue)
249
+ };
250
+ };
251
+ var belongsToStrategy = (row, strategy) => strategy == null || finiteString(row.strategy) === strategy;
252
+ var filterArtifactByStrategy = (artifact, strategy) => {
253
+ if (!strategy) return artifact;
254
+ const root = asRecord2(artifact) ?? {};
255
+ const runtime = runtimePayload(artifact);
256
+ return {
257
+ ...root,
258
+ runtime: {
259
+ ...runtime,
260
+ evaluations: asArray(runtime.evaluations).filter(
261
+ (item) => belongsToStrategy(
262
+ asRecord2(asRecord2(item)?.evaluation) ?? asRecord2(item) ?? {},
263
+ strategy
264
+ )
265
+ ),
266
+ signals: asArray(runtime.signals).filter(
267
+ (item) => belongsToStrategy(
268
+ asRecord2(asRecord2(item)?.signal) ?? asRecord2(item) ?? {},
269
+ strategy
270
+ )
271
+ ),
272
+ trades: asArray(runtime.trades).filter(
273
+ (item) => belongsToStrategy(
274
+ asRecord2(asRecord2(item)?.trade) ?? asRecord2(item) ?? {},
275
+ strategy
276
+ )
277
+ ),
278
+ evaluationStatsBuckets: asArray(runtime.evaluationStatsBuckets).filter(
279
+ (item) => belongsToStrategy(asRecord2(item) ?? {}, strategy)
280
+ )
281
+ }
282
+ };
283
+ };
218
284
  var countBy = (items, getter) => {
219
285
  const counts = {};
220
286
  for (const item of items) {
@@ -333,14 +399,43 @@ var getReplayComparison = (artifact) => {
333
399
  const root = asRecord2(artifact) ?? {};
334
400
  return asRecord2(asRecord2(root.replay)?.runtimeComparison) ?? null;
335
401
  };
402
+ var replayCountsForStrategy = (replayComparison, strategy) => {
403
+ if (strategy) {
404
+ const byStrategy = asRecord2(replayComparison?.byStrategy);
405
+ const strategyCounts = asRecord2(byStrategy?.[strategy]);
406
+ if (strategyCounts) {
407
+ return {
408
+ matched: finiteNumber(strategyCounts.matched) ?? 0,
409
+ backtestOnly: finiteNumber(strategyCounts.backtestOnly) ?? 0,
410
+ runtimeOnly: finiteNumber(strategyCounts.runtimeOnly) ?? 0
411
+ };
412
+ }
413
+ }
414
+ const counts = asRecord2(replayComparison?.counts);
415
+ return {
416
+ matched: finiteNumber(counts?.matched) ?? 0,
417
+ backtestOnly: finiteNumber(counts?.backtestOnly) ?? 0,
418
+ runtimeOnly: finiteNumber(counts?.runtimeOnly) ?? 0
419
+ };
420
+ };
336
421
  var getCalibrationSummary = (artifact) => {
337
422
  const root = asRecord2(artifact) ?? {};
338
423
  return asRecord2(asRecord2(root.summary)?.all) ?? null;
339
424
  };
425
+ var getProspectiveSummary = (artifact) => {
426
+ const root = asRecord2(artifact);
427
+ if (!root || root.reportType !== "strategy-prospective-evidence") return null;
428
+ return {
429
+ rawCoreExpectancy: finiteNumber(root.rawCoreExpectancy),
430
+ aiGateExpectancy: finiteNumber(root.aiGateExpectancy),
431
+ regimeCoverage: finiteNumber(root.regimeCoverage)
432
+ };
433
+ };
340
434
  var buildRuntimeScorecard = ({
341
435
  runtimeArtifact,
342
436
  replayEvidenceArtifact,
343
437
  calibrationArtifact,
438
+ prospectiveEvidenceArtifact,
344
439
  historyRuntimeArtifacts = [],
345
440
  thresholds = {
346
441
  minimumParityRatio: 0.95,
@@ -348,9 +443,19 @@ var buildRuntimeScorecard = ({
348
443
  minimumClosedTrades: 20,
349
444
  minimumExpectancy: 0
350
445
  },
351
- generatedAt = Date.now()
446
+ generatedAt = Date.now(),
447
+ strategy = null,
448
+ llmComparatorPolicy = "ai_approved_only"
352
449
  }) => {
353
- const rows = extractRuntimeRows(runtimeArtifact);
450
+ const scopedRuntimeArtifact = filterArtifactByStrategy(
451
+ runtimeArtifact,
452
+ strategy
453
+ );
454
+ const scopedHistoryArtifacts = historyRuntimeArtifacts.map(
455
+ (artifact) => filterArtifactByStrategy(artifact, strategy)
456
+ );
457
+ const rows = extractRuntimeRows(scopedRuntimeArtifact);
458
+ const runtimeLineage = buildRuntimeLineageSummary(rows);
354
459
  const window = runtimeWindow(runtimeArtifact);
355
460
  const startTime = finiteNumber(window.startTime) ?? generatedAt - 864e5;
356
461
  const endTime = finiteNumber(window.endTime) ?? generatedAt;
@@ -365,6 +470,20 @@ var buildRuntimeScorecard = ({
365
470
  })).filter(
366
471
  ({ decision }) => decision === "approved" || decision === "rejected"
367
472
  );
473
+ const llmEligibleEvaluations = llmComparatorPolicy === "disabled" ? [] : llmComparatorPolicy === "ai_approved_only" ? signalEvaluations.filter(
474
+ (evaluation) => finiteString(asRecord2(evaluation.aiAnalysis)?.gateDecision) === "approved"
475
+ ) : signalEvaluations;
476
+ const gateComparisons = llmEligibleEvaluations.map((evaluation) => {
477
+ const analysis = asRecord2(evaluation.aiAnalysis);
478
+ const gateDecision = finiteString(analysis?.gateDecision);
479
+ const llmDecision = finiteString(analysis?.llmDecision);
480
+ if (gateDecision !== "approved" && gateDecision !== "rejected" || llmDecision !== "approved" && llmDecision !== "rejected") {
481
+ return null;
482
+ }
483
+ return { gateDecision, llmDecision };
484
+ }).filter(
485
+ (comparison) => Boolean(comparison)
486
+ );
368
487
  const allocatorDecisions = signalEvaluations.map((evaluation) => asRecord2(evaluation.allocatorDecision)).filter((item) => item != null);
369
488
  const riskDecisions = signalEvaluations.map((evaluation) => asRecord2(evaluation.riskDecision)).filter((item) => item != null);
370
489
  const orderAttempts = rows.signals.filter(
@@ -375,27 +494,30 @@ var buildRuntimeScorecard = ({
375
494
  return trade.status === "closed" && exitTimestamp != null && exitTimestamp >= startTime && exitTimestamp < endTime;
376
495
  });
377
496
  const replayComparison = getReplayComparison(replayEvidenceArtifact);
378
- const replayCounts = asRecord2(replayComparison?.counts);
379
- const matched = finiteNumber(replayCounts?.matched) ?? 0;
380
- const backtestOnly = finiteNumber(replayCounts?.backtestOnly) ?? 0;
381
- const runtimeOnly = finiteNumber(replayCounts?.runtimeOnly) ?? 0;
497
+ const replayCounts = replayCountsForStrategy(replayComparison, strategy);
498
+ const matched = replayCounts.matched;
499
+ const backtestOnly = replayCounts.backtestOnly;
500
+ const runtimeOnly = replayCounts.runtimeOnly;
382
501
  const comparisonTotal = matched + backtestOnly + runtimeOnly;
383
502
  const parityRatio = comparisonTotal > 0 ? matched / comparisonTotal : null;
384
503
  const lineage = asRecord2(replayComparison?.lineage);
385
504
  const lineageReason = finiteString(lineage?.reason);
386
- const calibration = getCalibrationSummary(calibrationArtifact);
505
+ const calibrationRoot = asRecord2(calibrationArtifact) ?? {};
506
+ const calibrationSummary = asRecord2(calibrationRoot.summary);
507
+ const calibration = strategy ? asRecord2(asRecord2(calibrationSummary?.byStrategy)?.[strategy]) : getCalibrationSummary(calibrationArtifact);
387
508
  const actualSlippageBps = finiteNumber(
388
509
  asRecord2(calibration?.signalToFillAdverseBps)?.avg
389
510
  );
390
511
  const residualVsModelBps = finiteNumber(
391
512
  asRecord2(calibration?.residualVsCurrentModelBps)?.avg
392
513
  );
514
+ const prospective = getProspectiveSummary(prospectiveEvidenceArtifact);
393
515
  const historyTrades = dedupeTrades([
394
- ...historyRuntimeArtifacts,
395
- runtimeArtifact
516
+ ...scopedHistoryArtifacts,
517
+ scopedRuntimeArtifact
396
518
  ]);
397
519
  const currentDistributions = buildDistributions(rows);
398
- const previousArtifact = historyRuntimeArtifacts.map((artifact) => ({ artifact, window: runtimeWindow(artifact) })).filter(({ window: candidateWindow }) => {
520
+ const previousArtifact = scopedHistoryArtifacts.map((artifact) => ({ artifact, window: runtimeWindow(artifact) })).filter(({ window: candidateWindow }) => {
399
521
  const candidateEndTime = finiteNumber(candidateWindow.endTime);
400
522
  return candidateEndTime != null && candidateEndTime <= startTime;
401
523
  }).sort(
@@ -454,6 +576,8 @@ var buildRuntimeScorecard = ({
454
576
  schemaVersion: 1,
455
577
  generatedAt,
456
578
  window: { startTime, endTime },
579
+ strategy,
580
+ lineage: runtimeLineage,
457
581
  promotionStatus,
458
582
  thresholds,
459
583
  funnel: {
@@ -510,6 +634,24 @@ var buildRuntimeScorecard = ({
510
634
  currentClosedTrades.map((trade) => finiteNumber(trade.fundingFee))
511
635
  )
512
636
  },
637
+ gateComparison: {
638
+ policy: llmComparatorPolicy,
639
+ eligible: llmEligibleEvaluations.length,
640
+ compared: gateComparisons.length,
641
+ coverage: llmEligibleEvaluations.length ? round(gateComparisons.length / llmEligibleEvaluations.length) : null,
642
+ agreements: gateComparisons.filter(
643
+ (comparison) => comparison.gateDecision === comparison.llmDecision
644
+ ).length,
645
+ disagreements: gateComparisons.filter(
646
+ (comparison) => comparison.gateDecision !== comparison.llmDecision
647
+ ).length,
648
+ gateApprovedLlmRejected: gateComparisons.filter(
649
+ (comparison) => comparison.gateDecision === "approved" && comparison.llmDecision === "rejected"
650
+ ).length,
651
+ gateRejectedLlmApproved: gateComparisons.filter(
652
+ (comparison) => comparison.gateDecision === "rejected" && comparison.llmDecision === "approved"
653
+ ).length
654
+ },
513
655
  parity: {
514
656
  available: comparisonTotal > 0,
515
657
  matched,
@@ -523,6 +665,7 @@ var buildRuntimeScorecard = ({
523
665
  actualSignalToFillSlippageBps: actualSlippageBps,
524
666
  residualVsCurrentModelBps: residualVsModelBps
525
667
  },
668
+ prospective,
526
669
  rolling,
527
670
  distributions: currentDistributions,
528
671
  distributionChanges: {
@@ -575,6 +718,7 @@ Window: ${new Date(scorecard.window.startTime).toISOString()} \u2014 ${new Date(
575
718
  - Evaluations: ${scorecard.funnel.evaluations}
576
719
  - Core candidates: ${scorecard.funnel.coreCandidates}
577
720
  - Gate approvals/rejects: ${scorecard.funnel.gate.approved}/${scorecard.funnel.gate.rejected}
721
+ - AI / LLM disagreement: ${scorecard.gateComparison.disagreements}/${scorecard.gateComparison.compared}
578
722
  - Allocator approvals/rejects: ${scorecard.funnel.allocator.available ? `${scorecard.funnel.allocator.approved}/${scorecard.funnel.allocator.rejected}` : "unavailable"}
579
723
  - Risk approvals/rejects: ${scorecard.funnel.risk.available ? `${scorecard.funnel.risk.approved}/${scorecard.funnel.risk.rejected}` : "unavailable"}
580
724
  - Order attempts/failures: ${scorecard.funnel.orderAttempts}/${scorecard.funnel.orderFailures}
@@ -604,22 +748,649 @@ ${reactions}
604
748
  `;
605
749
  };
606
750
 
751
+ // src/lib/strategyRelease.ts
752
+ var import_node_crypto2 = require("crypto");
753
+ var import_promises3 = __toESM(require("fs/promises"));
754
+ var import_node_path3 = __toESM(require("path"));
755
+ var import_types = require("@tradejs/types");
756
+ var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
757
+ var SHA256_RE = /^[a-f0-9]{64}$/;
758
+ var LINEAGE_FINGERPRINT_RE = /^[a-f0-9]{16}$/;
759
+ var safeSegment2 = import_strategyReleaseEvidence.safeStrategyEvidenceSegment;
760
+ var compactTimestamp = import_strategyReleaseEvidence.compactStrategyEvidenceTimestamp;
761
+ var canonicalStrategyReleaseJson = (value) => (0, import_strategyReleaseEvidence.canonicalStrategyEvidenceJson)(value);
762
+ var strategyReleaseSha256 = (value) => (0, import_strategyReleaseEvidence.strategyEvidenceSha256)(value);
763
+ function assert(condition, message) {
764
+ if (!condition) throw new Error(`Invalid strategy release: ${message}`);
765
+ }
766
+ var assertFingerprint = (value, label) => assert(SHA256_RE.test(value), `${label} must be a lowercase SHA-256`);
767
+ var assertLineageFingerprint = (value, label) => assert(
768
+ LINEAGE_FINGERPRINT_RE.test(value),
769
+ `${label} must be a 16-character lowercase lineage fingerprint`
770
+ );
771
+ var releaseReasons = (input) => {
772
+ const reasons = [];
773
+ const requiredEvidenceKinds = [
774
+ "core_research",
775
+ "ai_gate",
776
+ "runtime_parity",
777
+ "execution_calibration"
778
+ ];
779
+ if (input.evidence.some((entry) => !entry.verified) || requiredEvidenceKinds.some(
780
+ (kind) => !input.evidence.some((entry) => entry.kind === kind)
781
+ )) {
782
+ reasons.push("EVIDENCE_INCOMPLETE");
783
+ }
784
+ if (!input.gates.coreEdgeVerified) reasons.push("NO_VERIFIED_CORE_EDGE");
785
+ if (!input.gates.aiGateAddsValue) reasons.push("AI_GATE_ADDS_NO_VALUE");
786
+ if (!input.gates.currentMarketSuitable) {
787
+ reasons.push("CURRENT_REGIME_UNSUITABLE");
788
+ }
789
+ if (!input.gates.runtimeParityVerified) {
790
+ reasons.push("RUNTIME_PARITY_BLOCKED");
791
+ }
792
+ if (!input.gates.executionModelVerified) {
793
+ reasons.push("EXECUTION_MODEL_UNSAFE");
794
+ }
795
+ if (input.researchBudget.hypothesisFamilies >= 3 && !input.gates.coreEdgeVerified) {
796
+ reasons.push("RESEARCH_BUDGET_EXHAUSTED");
797
+ }
798
+ return [...new Set(reasons)];
799
+ };
800
+ var deriveReleaseGates = (evidence) => {
801
+ const asserted = (name) => {
802
+ const values = evidence.filter((entry) => entry.verified).map((entry) => entry.releaseAssertions?.[name]).filter((value) => typeof value === "boolean");
803
+ return values.length > 0 && values.every(Boolean);
804
+ };
805
+ return {
806
+ coreEdgeVerified: asserted("coreEdgeVerified"),
807
+ aiGateAddsValue: asserted("aiGateAddsValue"),
808
+ currentMarketSuitable: asserted("currentMarketSuitable"),
809
+ runtimeParityVerified: asserted("runtimeParityVerified"),
810
+ executionModelVerified: asserted("executionModelVerified")
811
+ };
812
+ };
813
+ var verifyEvidenceComposition = (strategy, composition, evidence) => {
814
+ const requireEqual = (actual, expected, label, artifactId) => assert(
815
+ actual === expected,
816
+ `evidence ${artifactId} ${label} does not match the frozen composition`
817
+ );
818
+ for (const reference of evidence) {
819
+ const lineage = reference.lineage;
820
+ if (reference.kind === "core_research") {
821
+ requireEqual(
822
+ lineage?.strategy,
823
+ strategy,
824
+ "strategy",
825
+ reference.artifactId
826
+ );
827
+ requireEqual(
828
+ lineage?.coreConfigSha256,
829
+ composition.coreConfigSha256,
830
+ "coreConfigSha256",
831
+ reference.artifactId
832
+ );
833
+ requireEqual(
834
+ lineage?.gitSha,
835
+ composition.gitSha,
836
+ "gitSha",
837
+ reference.artifactId
838
+ );
839
+ requireEqual(lineage?.gitDirty, false, "gitDirty", reference.artifactId);
840
+ requireEqual(
841
+ lineage?.maxLossValue,
842
+ composition.maxLossValue,
843
+ "MAX_LOSS_VALUE",
844
+ reference.artifactId
845
+ );
846
+ assert(
847
+ lineage?.sourceSha256s.includes(composition.coreExportSha256),
848
+ `evidence ${reference.artifactId} does not include the frozen core export`
849
+ );
850
+ continue;
851
+ }
852
+ if (reference.kind === "ai_gate") {
853
+ requireEqual(
854
+ lineage?.strategy,
855
+ strategy,
856
+ "strategy",
857
+ reference.artifactId
858
+ );
859
+ requireEqual(
860
+ lineage?.gitSha,
861
+ composition.gitSha,
862
+ "gitSha",
863
+ reference.artifactId
864
+ );
865
+ requireEqual(lineage?.gitDirty, false, "gitDirty", reference.artifactId);
866
+ requireEqual(
867
+ lineage?.gateConfigIdsFingerprint,
868
+ composition.gateConfigIdsFingerprint,
869
+ "gateConfigIdsFingerprint",
870
+ reference.artifactId
871
+ );
872
+ requireEqual(
873
+ lineage?.gateFingerprint,
874
+ composition.gateFingerprint,
875
+ "gateFingerprint",
876
+ reference.artifactId
877
+ );
878
+ requireEqual(
879
+ lineage?.gateContextFingerprint,
880
+ composition.gateContextFingerprint,
881
+ "gateContextFingerprint",
882
+ reference.artifactId
883
+ );
884
+ assert(
885
+ lineage?.sourceSha256s.includes(composition.coreExportSha256),
886
+ `evidence ${reference.artifactId} does not use the frozen core export`
887
+ );
888
+ continue;
889
+ }
890
+ if (reference.kind === "runtime_parity") {
891
+ requireEqual(
892
+ lineage?.strategy,
893
+ strategy,
894
+ "strategy",
895
+ reference.artifactId
896
+ );
897
+ requireEqual(
898
+ lineage?.gitSha,
899
+ composition.gitSha,
900
+ "gitSha",
901
+ reference.artifactId
902
+ );
903
+ requireEqual(lineage?.gitDirty, false, "gitDirty", reference.artifactId);
904
+ requireEqual(
905
+ lineage?.gateFingerprint,
906
+ composition.gateFingerprint,
907
+ "gateFingerprint",
908
+ reference.artifactId
909
+ );
910
+ requireEqual(
911
+ lineage?.runtimeConfigFingerprint,
912
+ composition.runtimeConfigFingerprint,
913
+ "runtimeConfigFingerprint",
914
+ reference.artifactId
915
+ );
916
+ requireEqual(
917
+ lineage?.runtimeContextFingerprint,
918
+ composition.runtimeContextFingerprint,
919
+ "runtimeContextFingerprint",
920
+ reference.artifactId
921
+ );
922
+ requireEqual(
923
+ lineage?.maxLossValue,
924
+ composition.maxLossValue,
925
+ "MAX_LOSS_VALUE",
926
+ reference.artifactId
927
+ );
928
+ continue;
929
+ }
930
+ if (reference.kind === "execution_calibration") {
931
+ requireEqual(
932
+ lineage?.strategy,
933
+ strategy,
934
+ "strategy",
935
+ reference.artifactId
936
+ );
937
+ requireEqual(
938
+ lineage?.gitSha,
939
+ composition.gitSha,
940
+ "gitSha",
941
+ reference.artifactId
942
+ );
943
+ requireEqual(lineage?.gitDirty, false, "gitDirty", reference.artifactId);
944
+ requireEqual(
945
+ lineage?.gateFingerprint,
946
+ composition.gateFingerprint,
947
+ "gateFingerprint",
948
+ reference.artifactId
949
+ );
950
+ requireEqual(
951
+ lineage?.runtimeConfigFingerprint,
952
+ composition.runtimeConfigFingerprint,
953
+ "runtimeConfigFingerprint",
954
+ reference.artifactId
955
+ );
956
+ requireEqual(
957
+ lineage?.runtimeContextFingerprint,
958
+ composition.runtimeContextFingerprint,
959
+ "runtimeContextFingerprint",
960
+ reference.artifactId
961
+ );
962
+ requireEqual(
963
+ lineage?.maxLossValue,
964
+ composition.maxLossValue,
965
+ "MAX_LOSS_VALUE",
966
+ reference.artifactId
967
+ );
968
+ }
969
+ }
970
+ };
971
+ var createStrategyReleaseManifest = (input) => {
972
+ assert(input.strategy.trim(), "strategy is required");
973
+ assert(
974
+ Number.isFinite(input.createdAt),
975
+ "createdAt must be a finite timestamp"
976
+ );
977
+ assert(
978
+ input.researchBudget.hypothesisFamilies <= 3,
979
+ "at most 3 hypothesis families are allowed"
980
+ );
981
+ assert(
982
+ input.researchBudget.maximumVariantsPerFamily <= 5,
983
+ "at most 5 variants per family are allowed"
984
+ );
985
+ assert(
986
+ input.researchBudget.isolatedLongFinalists <= 1,
987
+ "at most one isolated-long finalist is allowed"
988
+ );
989
+ assert(
990
+ input.researchBudget.aiGateTuningRounds <= 1,
991
+ "at most one AI-gate tuning round is allowed"
992
+ );
993
+ assert(input.marketWindow.cacheOnly, "market window must be cache-only");
994
+ assert(
995
+ input.marketWindow.endTime > input.marketWindow.startTime,
996
+ "market window is invalid"
997
+ );
998
+ assert(input.composition.maxLossValue > 0, "MAX_LOSS_VALUE must be positive");
999
+ assert(
1000
+ input.composition.longEnabled && input.composition.shortEnabled,
1001
+ "LONG and SHORT must both remain enabled"
1002
+ );
1003
+ assert(
1004
+ Number.isInteger(input.monitoring.minimumProspectiveClosedTrades) && input.monitoring.minimumProspectiveClosedTrades > 0,
1005
+ "monitoring.minimumProspectiveClosedTrades must be positive"
1006
+ );
1007
+ assert(
1008
+ Number.isFinite(input.monitoring.minimumParityRatio) && input.monitoring.minimumParityRatio >= 0 && input.monitoring.minimumParityRatio <= 1,
1009
+ "monitoring.minimumParityRatio must be between 0 and 1"
1010
+ );
1011
+ assert(
1012
+ Number.isFinite(input.monitoring.maximumOrderFailureRate) && input.monitoring.maximumOrderFailureRate >= 0 && input.monitoring.maximumOrderFailureRate <= 1,
1013
+ "monitoring.maximumOrderFailureRate must be between 0 and 1"
1014
+ );
1015
+ assert(
1016
+ Number.isFinite(input.monitoring.minimumRegimeCoverage) && input.monitoring.minimumRegimeCoverage >= 0 && input.monitoring.minimumRegimeCoverage <= 1,
1017
+ "monitoring.minimumRegimeCoverage must be between 0 and 1"
1018
+ );
1019
+ assert(
1020
+ input.monitoring.drawdownEnvelopes.length > 0 && input.monitoring.drawdownEnvelopes.every(
1021
+ (entry) => Number.isInteger(entry.days) && entry.days > 0 && Number.isFinite(entry.p95) && Number.isFinite(entry.maximum) && entry.p95 >= 0 && entry.maximum >= entry.p95
1022
+ ),
1023
+ "monitoring drawdown envelopes are invalid"
1024
+ );
1025
+ assertFingerprint(input.composition.coreConfigSha256, "coreConfigSha256");
1026
+ assertFingerprint(input.composition.coreExportSha256, "coreExportSha256");
1027
+ assertLineageFingerprint(
1028
+ input.composition.gateConfigIdsFingerprint,
1029
+ "gateConfigIdsFingerprint"
1030
+ );
1031
+ assertLineageFingerprint(
1032
+ input.composition.runtimeConfigFingerprint,
1033
+ "runtimeConfigFingerprint"
1034
+ );
1035
+ assertLineageFingerprint(
1036
+ input.composition.gateFingerprint,
1037
+ "gateFingerprint"
1038
+ );
1039
+ assertLineageFingerprint(
1040
+ input.composition.gateContextFingerprint,
1041
+ "gateContextFingerprint"
1042
+ );
1043
+ assertLineageFingerprint(
1044
+ input.composition.runtimeContextFingerprint,
1045
+ "runtimeContextFingerprint"
1046
+ );
1047
+ assertFingerprint(input.marketWindow.universeSha256, "universeSha256");
1048
+ for (const entry of input.evidence) {
1049
+ assertFingerprint(entry.sha256, `evidence ${entry.artifactId}`);
1050
+ }
1051
+ verifyEvidenceComposition(input.strategy, input.composition, input.evidence);
1052
+ const evidenceGates = deriveReleaseGates(input.evidence);
1053
+ assert(
1054
+ canonicalStrategyReleaseJson(input.gates) === canonicalStrategyReleaseJson(evidenceGates),
1055
+ "release gates must be derived from verified evidence assertions"
1056
+ );
1057
+ const compositionIdentity = {
1058
+ strategy: input.strategy,
1059
+ gitSha: input.composition.gitSha,
1060
+ coreConfigSha256: input.composition.coreConfigSha256,
1061
+ coreExportSha256: input.composition.coreExportSha256,
1062
+ gateConfigIdsFingerprint: input.composition.gateConfigIdsFingerprint,
1063
+ runtimeConfigFingerprint: input.composition.runtimeConfigFingerprint,
1064
+ gateFingerprint: input.composition.gateFingerprint,
1065
+ gateContextFingerprint: input.composition.gateContextFingerprint,
1066
+ runtimeContextFingerprint: input.composition.runtimeContextFingerprint,
1067
+ maxLossValue: input.composition.maxLossValue
1068
+ };
1069
+ const compositionId = `${safeSegment2(input.strategy, "strategy")}_${strategyReleaseSha256(compositionIdentity).slice(0, 16)}`;
1070
+ const reasons = releaseReasons({ ...input, gates: evidenceGates });
1071
+ const economicFailure = reasons.some(
1072
+ (reason) => [
1073
+ "NO_VERIFIED_CORE_EDGE",
1074
+ "AI_GATE_ADDS_NO_VALUE",
1075
+ "CURRENT_REGIME_UNSUITABLE",
1076
+ "RESEARCH_BUDGET_EXHAUSTED"
1077
+ ].includes(reason)
1078
+ );
1079
+ const evidenceFailure = reasons.some(
1080
+ (reason) => [
1081
+ "EVIDENCE_INCOMPLETE",
1082
+ "RUNTIME_PARITY_BLOCKED",
1083
+ "EXECUTION_MODEL_UNSAFE"
1084
+ ].includes(reason)
1085
+ );
1086
+ const verdict = evidenceFailure ? "INSUFFICIENT_EVIDENCE" : economicFailure ? "UNSUITABLE_FOR_CURRENT_MARKET" : "READY_FOR_RUNTIME";
1087
+ const releaseIdentity = {
1088
+ strategy: input.strategy,
1089
+ createdAt: input.createdAt,
1090
+ compositionId,
1091
+ evidence: input.evidence.map(({ kind, artifactId, sha256: sha2562 }) => ({
1092
+ kind,
1093
+ artifactId,
1094
+ sha256: sha2562
1095
+ })),
1096
+ verdict
1097
+ };
1098
+ return {
1099
+ schema: import_types.STRATEGY_RELEASE_SCHEMA,
1100
+ releaseId: `${safeSegment2(input.strategy, "strategy")}_${compactTimestamp(input.createdAt)}_${strategyReleaseSha256(releaseIdentity).slice(0, 16)}`,
1101
+ strategy: input.strategy,
1102
+ createdAt: input.createdAt,
1103
+ composition: { ...input.composition, compositionId },
1104
+ marketWindow: input.marketWindow,
1105
+ researchBudget: input.researchBudget,
1106
+ evidence: input.evidence,
1107
+ gates: evidenceGates,
1108
+ monitoring: input.monitoring,
1109
+ verdict: {
1110
+ status: verdict,
1111
+ reasons,
1112
+ summary: input.summary
1113
+ },
1114
+ prospective: input.prospective
1115
+ };
1116
+ };
1117
+ var verifyStrategyReleaseEnvelope = async (valueOrPath) => {
1118
+ const value = typeof valueOrPath === "string" ? JSON.parse(await import_promises3.default.readFile(valueOrPath, "utf8")) : valueOrPath;
1119
+ const envelope = value;
1120
+ assert(
1121
+ envelope?.schema === "tradejs-strategy-release-envelope/v1",
1122
+ "release envelope schema mismatch"
1123
+ );
1124
+ assert(
1125
+ envelope.releaseId === envelope.manifest?.releaseId,
1126
+ "releaseId mismatch"
1127
+ );
1128
+ assert(
1129
+ strategyReleaseSha256(envelope.manifest) === envelope.manifestSha256,
1130
+ "release manifest checksum mismatch"
1131
+ );
1132
+ const manifest = envelope.manifest;
1133
+ assert(
1134
+ manifest.schema === import_types.STRATEGY_RELEASE_SCHEMA,
1135
+ "manifest schema mismatch"
1136
+ );
1137
+ const rebuilt = createStrategyReleaseManifest({
1138
+ strategy: manifest.strategy,
1139
+ createdAt: manifest.createdAt,
1140
+ composition: {
1141
+ gitSha: manifest.composition.gitSha,
1142
+ coreConfigSha256: manifest.composition.coreConfigSha256,
1143
+ coreExportSha256: manifest.composition.coreExportSha256,
1144
+ gateConfigIdsFingerprint: manifest.composition.gateConfigIdsFingerprint,
1145
+ runtimeConfigFingerprint: manifest.composition.runtimeConfigFingerprint,
1146
+ gateFingerprint: manifest.composition.gateFingerprint,
1147
+ gateContextFingerprint: manifest.composition.gateContextFingerprint,
1148
+ runtimeContextFingerprint: manifest.composition.runtimeContextFingerprint,
1149
+ maxLossValue: manifest.composition.maxLossValue,
1150
+ longEnabled: manifest.composition.longEnabled,
1151
+ shortEnabled: manifest.composition.shortEnabled
1152
+ },
1153
+ marketWindow: manifest.marketWindow,
1154
+ researchBudget: manifest.researchBudget,
1155
+ evidence: manifest.evidence,
1156
+ gates: manifest.gates,
1157
+ monitoring: manifest.monitoring,
1158
+ summary: manifest.verdict.summary,
1159
+ prospective: manifest.prospective
1160
+ });
1161
+ assert(
1162
+ canonicalStrategyReleaseJson(rebuilt) === canonicalStrategyReleaseJson(manifest),
1163
+ "release manifest contains self-declared or inconsistent derived fields"
1164
+ );
1165
+ return envelope;
1166
+ };
1167
+ var verifyStrategyEvidenceMarkerEnvelope = async (valueOrPath) => {
1168
+ const value = typeof valueOrPath === "string" ? JSON.parse(await import_promises3.default.readFile(valueOrPath, "utf8")) : valueOrPath;
1169
+ return (0, import_strategyReleaseEvidence.verifyStrategyEvidenceMarkerEnvelope)(value);
1170
+ };
1171
+ var writeJsonAtomic = async (filePath, value) => {
1172
+ const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
1173
+ await import_promises3.default.mkdir(import_node_path3.default.dirname(filePath), { recursive: true });
1174
+ await import_promises3.default.writeFile(
1175
+ temporaryPath,
1176
+ `${canonicalStrategyReleaseJson(value)}
1177
+ `,
1178
+ "utf8"
1179
+ );
1180
+ await import_promises3.default.rename(temporaryPath, filePath);
1181
+ };
1182
+ var buildStrategyLiveDiagnosis = (input) => {
1183
+ const minimumClosedTrades = input.minimumClosedTrades ?? 20;
1184
+ const minimumParityRatio = input.minimumParityRatio ?? 0.95;
1185
+ const maximumOrderFailureRate = input.maximumOrderFailureRate ?? 0.05;
1186
+ const minimumRegimeCoverage = input.minimumRegimeCoverage ?? 0.5;
1187
+ const hasExplicitRiskScale = input.riskScaleComparable != null;
1188
+ const comparisonObservedDrawdown = hasExplicitRiskScale ? input.riskScaleComparable ? input.normalizedObservedDrawdown ?? null : null : input.observedDrawdown;
1189
+ const runtimeDivergence = !input.lineageComparable || input.parityRatio != null && input.parityRatio < minimumParityRatio || input.orderFailureRate != null && input.orderFailureRate > maximumOrderFailureRate;
1190
+ const insufficient = input.closedTrades < minimumClosedTrades || input.parityRatio == null || comparisonObservedDrawdown == null || input.historicalDrawdownP95 == null;
1191
+ const expected = !runtimeDivergence && !insufficient && comparisonObservedDrawdown <= input.historicalDrawdownP95;
1192
+ const verdict = runtimeDivergence ? "RUNTIME_DIVERGENCE" : insufficient ? "INSUFFICIENT_EVIDENCE" : expected ? "EXPECTED_DRAWDOWN" : "GENERALIZATION_FAILURE";
1193
+ const attributionIncomplete = input.rawCoreExpectancyDelta == null || input.aiGateAddedValue == null || input.regimeCoverage == null;
1194
+ const attributedVerdict = verdict === "GENERALIZATION_FAILURE" && attributionIncomplete ? "INSUFFICIENT_EVIDENCE" : verdict;
1195
+ 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";
1196
+ 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.";
1197
+ const recommendations = attributedVerdict === "RUNTIME_DIVERGENCE" ? [
1198
+ "Resolve lineage and replay parity mismatches before evaluating strategy economics.",
1199
+ "Inspect order failures and execution residuals against the frozen execution model."
1200
+ ] : attributedVerdict === "EXPECTED_DRAWDOWN" ? [
1201
+ "Keep the frozen composition unchanged and continue prospective evidence collection."
1202
+ ] : attributedVerdict === "GENERALIZATION_FAILURE" ? [
1203
+ "Do not retune the live cohort; open a new immutable research lineage.",
1204
+ "Compare raw-core decay, AI-gate added value, and causal regime coverage."
1205
+ ] : [
1206
+ 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."
1207
+ ];
1208
+ return {
1209
+ schema: import_types.STRATEGY_LIVE_DIAGNOSIS_SCHEMA,
1210
+ strategy: input.strategy,
1211
+ compositionId: input.compositionId,
1212
+ createdAt: input.createdAt,
1213
+ verdict: attributedVerdict,
1214
+ subtype,
1215
+ confidence: runtimeDivergence || input.closedTrades >= 50 ? "high" : input.closedTrades >= 20 ? "medium" : "low",
1216
+ evidence: {
1217
+ lineageComparable: input.lineageComparable,
1218
+ riskScaleComparable: input.riskScaleComparable ?? false,
1219
+ releaseMaxLossValue: input.releaseMaxLossValue ?? null,
1220
+ runtimeMaxLossValue: input.runtimeMaxLossValue ?? null,
1221
+ riskScaleRatio: input.riskScaleRatio ?? null,
1222
+ parityRatio: input.parityRatio,
1223
+ orderFailureRate: input.orderFailureRate,
1224
+ observedDrawdown: input.observedDrawdown,
1225
+ normalizedObservedDrawdown: input.normalizedObservedDrawdown ?? null,
1226
+ historicalDrawdownP95: input.historicalDrawdownP95,
1227
+ historicalDrawdownMaximum: input.historicalDrawdownMaximum,
1228
+ closedTrades: input.closedTrades,
1229
+ rawCoreExpectancyDelta: input.rawCoreExpectancyDelta,
1230
+ aiGateAddedValue: input.aiGateAddedValue,
1231
+ regimeCoverage: input.regimeCoverage,
1232
+ overfitProbability: input.overfitProbability
1233
+ },
1234
+ explanation,
1235
+ recommendations
1236
+ };
1237
+ };
1238
+ var buildStrategyLiveDiagnosisFromScorecard = ({
1239
+ manifest,
1240
+ scorecard,
1241
+ days
1242
+ }) => {
1243
+ const rolling = scorecard.rolling.find((entry) => entry.days === days);
1244
+ const envelope = manifest.monitoring.drawdownEnvelopes.find(
1245
+ (entry) => entry.days === days
1246
+ );
1247
+ const orderFailureRate = scorecard.funnel.orderAttempts ? scorecard.funnel.orderFailures / scorecard.funnel.orderAttempts : null;
1248
+ const lineage = scorecard.lineage;
1249
+ 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;
1250
+ const releaseMaxLossValue = manifest.composition.maxLossValue;
1251
+ const runtimeMaxLossValue = lineage?.maxLossValue ?? null;
1252
+ const riskScaleComparable = Number.isFinite(releaseMaxLossValue) && releaseMaxLossValue > 0 && runtimeMaxLossValue != null && Number.isFinite(runtimeMaxLossValue) && runtimeMaxLossValue > 0;
1253
+ const riskScaleRatio = riskScaleComparable ? runtimeMaxLossValue / releaseMaxLossValue : null;
1254
+ const normalizedObservedDrawdown = rolling != null && riskScaleRatio != null ? rolling.maxDrawdown / riskScaleRatio : null;
1255
+ const normalizeRuntimeEconomics = (value) => value != null && riskScaleRatio != null ? value / riskScaleRatio : null;
1256
+ return buildStrategyLiveDiagnosis({
1257
+ strategy: manifest.strategy,
1258
+ compositionId: manifest.composition.compositionId,
1259
+ createdAt: scorecard.generatedAt,
1260
+ lineageComparable,
1261
+ riskScaleComparable,
1262
+ releaseMaxLossValue,
1263
+ runtimeMaxLossValue,
1264
+ riskScaleRatio,
1265
+ parityRatio: scorecard.parity.ratio,
1266
+ orderFailureRate,
1267
+ observedDrawdown: rolling?.maxDrawdown ?? null,
1268
+ normalizedObservedDrawdown,
1269
+ historicalDrawdownP95: envelope?.p95 ?? null,
1270
+ historicalDrawdownMaximum: envelope?.maximum ?? null,
1271
+ closedTrades: rolling?.closedTrades ?? 0,
1272
+ rawCoreExpectancyDelta: scorecard.prospective?.rawCoreExpectancy != null && manifest.monitoring.rawCoreExpectancy != null ? normalizeRuntimeEconomics(scorecard.prospective.rawCoreExpectancy) - manifest.monitoring.rawCoreExpectancy : null,
1273
+ aiGateAddedValue: scorecard.prospective?.rawCoreExpectancy != null && scorecard.prospective.aiGateExpectancy != null ? normalizeRuntimeEconomics(scorecard.prospective.aiGateExpectancy) - normalizeRuntimeEconomics(scorecard.prospective.rawCoreExpectancy) : null,
1274
+ regimeCoverage: scorecard.prospective?.regimeCoverage ?? null,
1275
+ overfitProbability: manifest.monitoring.overfitProbability,
1276
+ minimumClosedTrades: manifest.monitoring.minimumProspectiveClosedTrades,
1277
+ minimumParityRatio: manifest.monitoring.minimumParityRatio,
1278
+ maximumOrderFailureRate: manifest.monitoring.maximumOrderFailureRate,
1279
+ minimumRegimeCoverage: manifest.monitoring.minimumRegimeCoverage
1280
+ });
1281
+ };
1282
+ var publishStrategyLiveDiagnosis = async ({
1283
+ rootDir,
1284
+ diagnosis,
1285
+ sourceArtifacts,
1286
+ composition
1287
+ }) => {
1288
+ const diagnosisSha256 = strategyReleaseSha256(diagnosis);
1289
+ const diagnosisId = `${safeSegment2(diagnosis.strategy, "strategy")}_${compactTimestamp(diagnosis.createdAt)}_${diagnosisSha256.slice(0, 16)}`;
1290
+ const envelope = {
1291
+ schema: "tradejs-strategy-live-diagnosis-envelope/v1",
1292
+ diagnosisId,
1293
+ diagnosisSha256,
1294
+ diagnosis
1295
+ };
1296
+ const common = {
1297
+ timestamp: diagnosis.createdAt,
1298
+ artifactId: diagnosisId,
1299
+ artifactSha256: diagnosisSha256,
1300
+ compositionId: diagnosis.compositionId,
1301
+ ...composition ? {
1302
+ gitSha: composition.gitSha,
1303
+ configFingerprint: composition.runtimeConfigFingerprint,
1304
+ gateFingerprint: composition.gateFingerprint,
1305
+ contextFingerprint: composition.runtimeContextFingerprint,
1306
+ maxLossValue: composition.maxLossValue
1307
+ } : {}
1308
+ };
1309
+ const markers = [
1310
+ ...diagnosis.evidence.runtimeMaxLossValue != null && diagnosis.evidence.runtimeMaxLossValue !== diagnosis.evidence.releaseMaxLossValue ? [
1311
+ {
1312
+ ...common,
1313
+ id: `${diagnosisId}:loss`,
1314
+ type: "L",
1315
+ label: "Trade loss value changed",
1316
+ summary: `MAX_LOSS_VALUE ${diagnosis.evidence.runtimeMaxLossValue}`,
1317
+ maxLossValue: diagnosis.evidence.runtimeMaxLossValue,
1318
+ severity: "info"
1319
+ }
1320
+ ] : [],
1321
+ ...diagnosis.verdict === "RUNTIME_DIVERGENCE" ? [
1322
+ {
1323
+ ...common,
1324
+ id: `${diagnosisId}:parity`,
1325
+ type: "P",
1326
+ label: "Runtime parity mismatch",
1327
+ summary: diagnosis.explanation,
1328
+ severity: "blocking"
1329
+ }
1330
+ ] : [],
1331
+ {
1332
+ ...common,
1333
+ id: `${diagnosisId}:recommendation`,
1334
+ type: "R",
1335
+ label: "Live diagnosis",
1336
+ summary: `${diagnosis.verdict}: ${diagnosis.explanation}`,
1337
+ severity: diagnosis.verdict === "EXPECTED_DRAWDOWN" ? "info" : "warning"
1338
+ }
1339
+ ];
1340
+ const markerEnvelope = (0, import_strategyReleaseEvidence.createStrategyEvidenceMarkerEnvelope)({
1341
+ strategy: diagnosis.strategy,
1342
+ createdAt: diagnosis.createdAt,
1343
+ markers,
1344
+ sourceArtifacts
1345
+ });
1346
+ const diagnosisPath = import_node_path3.default.join(
1347
+ rootDir,
1348
+ "diagnoses",
1349
+ safeSegment2(diagnosis.strategy, "strategy"),
1350
+ `${diagnosisId}.json`
1351
+ );
1352
+ const markerPath = import_node_path3.default.join(
1353
+ rootDir,
1354
+ "markers",
1355
+ safeSegment2(diagnosis.strategy, "strategy"),
1356
+ `${markerEnvelope.artifactId}.json`
1357
+ );
1358
+ await Promise.all([
1359
+ writeJsonAtomic(diagnosisPath, envelope),
1360
+ writeJsonAtomic(markerPath, markerEnvelope)
1361
+ ]);
1362
+ await verifyStrategyEvidenceMarkerEnvelope(markerPath);
1363
+ return { diagnosisPath, markerPath, envelope, markerEnvelope };
1364
+ };
1365
+
607
1366
  // src/scripts/runtimeScorecard.ts
608
- import_args.default.option("runtimeEvidence", "Verified runtime evidence JSON");
609
- import_args.default.option("replayEvidence", "Replay runtime evidence JSON");
610
- import_args.default.option("calibration", "Execution calibration JSON");
611
- import_args.default.option("historyDir", "Directory containing verified evidence bundles");
1367
+ import_args.default.option(["u", "runtimeEvidence"], "Verified runtime evidence JSON");
1368
+ import_args.default.option(["r", "replayEvidence"], "Replay runtime evidence JSON");
1369
+ import_args.default.option(["c", "calibration"], "Execution calibration JSON");
612
1370
  import_args.default.option(
613
- "evidenceDir",
1371
+ ["q", "prospectiveEvidence"],
1372
+ "Verified prospective raw-core/gate/regime summary JSON"
1373
+ );
1374
+ import_args.default.option(["s", "strategy"], "Exact strategy to isolate in the scorecard");
1375
+ import_args.default.option(
1376
+ ["H", "historyDir"],
1377
+ "Directory containing verified evidence bundles"
1378
+ );
1379
+ import_args.default.option(
1380
+ ["E", "evidenceDir"],
614
1381
  "Local evidence storage root used for processing receipts",
615
1382
  process.env.RUNTIME_EVIDENCE_LOCAL_DIR || "data/runtime-evidence"
616
1383
  );
617
1384
  import_args.default.option(
618
- "deployment",
1385
+ ["d", "deployment"],
619
1386
  "Deployment id used for processing receipts",
620
1387
  process.env.RUNTIME_EVIDENCE_DEPLOYMENT_ID || "production"
621
1388
  );
622
- import_args.default.option("markProcessed", "Write a processing receipt on success", false);
1389
+ import_args.default.option(
1390
+ ["M", "markProcessed"],
1391
+ "Write a processing receipt on success",
1392
+ false
1393
+ );
623
1394
  import_args.default.option(
624
1395
  ["P", "minimumParityRatio"],
625
1396
  "Minimum acceptable replay parity",
@@ -637,13 +1408,28 @@ import_args.default.option(
637
1408
  );
638
1409
  import_args.default.option(["X", "minimumExpectancy"], "Minimum acceptable 7d expectancy", 0);
639
1410
  import_args.default.option(["o", "out"], "Output JSON path", "output/runtime-scorecard.json");
1411
+ import_args.default.option(
1412
+ ["m", "releaseManifest"],
1413
+ "Verified strategy release envelope JSON"
1414
+ );
1415
+ import_args.default.option(["D", "diagnosisDays"], "Equal-length release drawdown window", 7);
1416
+ import_args.default.option(
1417
+ ["z", "strategyReleaseRoot"],
1418
+ "Publish advisory diagnosis and immutable chart markers under this root"
1419
+ );
640
1420
  var flags = import_args.default.parse(process.argv);
641
1421
  var projectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
642
1422
  var resolveOptionalPath = (value) => {
643
1423
  const raw = String(value ?? "").trim();
644
- return raw ? import_node_path3.default.resolve(projectRoot, raw) : null;
1424
+ return raw ? import_node_path4.default.resolve(projectRoot, raw) : null;
1425
+ };
1426
+ var readJson = async (filePath) => filePath ? JSON.parse(await import_promises4.default.readFile(filePath, "utf8")) : void 0;
1427
+ var assertReportType = (artifact, expected, label) => {
1428
+ if (artifact != null && (!artifact || typeof artifact !== "object" || Array.isArray(artifact) || artifact.reportType !== expected)) {
1429
+ throw new Error(`${label} must be a ${expected} artifact`);
1430
+ }
645
1431
  };
646
- var readJson = async (filePath) => filePath ? JSON.parse(await import_promises3.default.readFile(filePath, "utf8")) : void 0;
1432
+ var sha256File = async (filePath) => (0, import_node_crypto3.createHash)("sha256").update(await import_promises4.default.readFile(filePath)).digest("hex");
647
1433
  var loadHistoryArtifacts = async (historyDir) => {
648
1434
  if (!historyDir) return [];
649
1435
  const bundleDirs = await discoverRuntimeEvidenceBundles(historyDir);
@@ -660,48 +1446,114 @@ var runtimeScorecard = async () => {
660
1446
  }
661
1447
  const replayEvidencePath = resolveOptionalPath(flags.replayEvidence);
662
1448
  const calibrationPath = resolveOptionalPath(flags.calibration);
1449
+ const prospectiveEvidencePath = resolveOptionalPath(
1450
+ flags.prospectiveEvidence
1451
+ );
663
1452
  const historyDir = resolveOptionalPath(flags.historyDir);
1453
+ const releaseManifestPath = resolveOptionalPath(flags.releaseManifest);
664
1454
  const [
665
1455
  runtimeArtifact,
666
1456
  replayEvidenceArtifact,
667
1457
  calibrationArtifact,
1458
+ prospectiveEvidenceArtifact,
668
1459
  history
669
1460
  ] = await Promise.all([
670
1461
  readJson(runtimeEvidencePath),
671
1462
  readJson(replayEvidencePath),
672
1463
  readJson(calibrationPath),
1464
+ readJson(prospectiveEvidencePath),
673
1465
  loadHistoryArtifacts(historyDir)
674
1466
  ]);
1467
+ assertReportType(runtimeArtifact, "runtime-evidence", "runtimeEvidence");
1468
+ assertReportType(
1469
+ replayEvidenceArtifact,
1470
+ "replay-runtime-evidence",
1471
+ "replayEvidence"
1472
+ );
1473
+ assertReportType(calibrationArtifact, "execution-calibration", "calibration");
1474
+ assertReportType(
1475
+ prospectiveEvidenceArtifact,
1476
+ "strategy-prospective-evidence",
1477
+ "prospectiveEvidence"
1478
+ );
1479
+ const verifiedRelease = releaseManifestPath ? await verifyStrategyReleaseEnvelope(releaseManifestPath) : null;
1480
+ const requestedStrategy = String(flags.strategy ?? "").trim() || null;
1481
+ if (verifiedRelease && requestedStrategy && requestedStrategy !== verifiedRelease.manifest.strategy) {
1482
+ throw new Error(
1483
+ `Requested strategy ${requestedStrategy} does not match release ${verifiedRelease.manifest.strategy}`
1484
+ );
1485
+ }
1486
+ const scorecardStrategy = requestedStrategy ?? verifiedRelease?.manifest.strategy ?? null;
675
1487
  const scorecard = buildRuntimeScorecard({
676
1488
  runtimeArtifact,
677
1489
  replayEvidenceArtifact,
678
1490
  calibrationArtifact,
1491
+ prospectiveEvidenceArtifact,
679
1492
  historyRuntimeArtifacts: history,
680
1493
  thresholds: {
681
1494
  minimumParityRatio: Number(flags.minimumParityRatio),
682
1495
  maximumSlippageResidualBps: Number(flags.maximumSlippageResidualBps),
683
1496
  minimumClosedTrades: Number(flags.minimumClosedTrades),
684
1497
  minimumExpectancy: Number(flags.minimumExpectancy)
685
- }
1498
+ },
1499
+ strategy: scorecardStrategy,
1500
+ llmComparatorPolicy: verifiedRelease?.manifest.prospective.llmComparatorPolicy ?? "ai_approved_only"
686
1501
  });
687
- const outPath = import_node_path3.default.resolve(projectRoot, String(flags.out));
1502
+ const outPath = import_node_path4.default.resolve(projectRoot, String(flags.out));
688
1503
  const markdownPath = outPath.replace(/\.json$/i, "") + ".md";
689
- await import_promises3.default.mkdir(import_node_path3.default.dirname(outPath), { recursive: true });
1504
+ await import_promises4.default.mkdir(import_node_path4.default.dirname(outPath), { recursive: true });
690
1505
  await Promise.all([
691
- import_promises3.default.writeFile(outPath, `${JSON.stringify(scorecard, null, 2)}
1506
+ import_promises4.default.writeFile(outPath, `${JSON.stringify(scorecard, null, 2)}
692
1507
  `, "utf8"),
693
- import_promises3.default.writeFile(
1508
+ import_promises4.default.writeFile(
694
1509
  markdownPath,
695
1510
  formatRuntimeScorecardMarkdown(scorecard),
696
1511
  "utf8"
697
1512
  )
698
1513
  ]);
1514
+ const diagnosis = verifiedRelease ? buildStrategyLiveDiagnosisFromScorecard({
1515
+ manifest: verifiedRelease.manifest,
1516
+ scorecard,
1517
+ days: Number(flags.diagnosisDays)
1518
+ }) : null;
1519
+ const diagnosisPath = diagnosis ? outPath.replace(/\.json$/i, "") + ".diagnosis.json" : null;
1520
+ const strategyReleaseRoot = resolveOptionalPath(flags.strategyReleaseRoot);
1521
+ if (diagnosis && diagnosisPath) {
1522
+ await import_promises4.default.writeFile(
1523
+ diagnosisPath,
1524
+ `${JSON.stringify(diagnosis, null, 2)}
1525
+ `,
1526
+ "utf8"
1527
+ );
1528
+ if (strategyReleaseRoot) {
1529
+ const sourcePaths = [
1530
+ runtimeEvidencePath,
1531
+ replayEvidencePath,
1532
+ calibrationPath,
1533
+ prospectiveEvidencePath,
1534
+ releaseManifestPath,
1535
+ outPath
1536
+ ].filter((value) => Boolean(value));
1537
+ await publishStrategyLiveDiagnosis({
1538
+ rootDir: strategyReleaseRoot,
1539
+ diagnosis,
1540
+ composition: verifiedRelease?.manifest.composition,
1541
+ sourceArtifacts: await Promise.all(
1542
+ sourcePaths.map(async (sourcePath) => ({
1543
+ artifactId: import_node_path4.default.basename(sourcePath),
1544
+ path: sourcePath,
1545
+ sha256: await sha256File(sourcePath)
1546
+ }))
1547
+ )
1548
+ });
1549
+ }
1550
+ }
699
1551
  let receiptPath2 = null;
700
1552
  if (flags.markProcessed) {
701
1553
  receiptPath2 = await markRuntimeEvidenceBundleProcessed({
702
- evidenceRoot: import_node_path3.default.resolve(projectRoot, String(flags.evidenceDir)),
1554
+ evidenceRoot: import_node_path4.default.resolve(projectRoot, String(flags.evidenceDir)),
703
1555
  deploymentId: String(flags.deployment),
704
- bundleDir: import_node_path3.default.dirname(runtimeEvidencePath),
1556
+ bundleDir: import_node_path4.default.dirname(runtimeEvidencePath),
705
1557
  scorecardPath: outPath
706
1558
  });
707
1559
  }
@@ -716,7 +1568,9 @@ var runtimeScorecard = async () => {
716
1568
  execution: scorecard.execution,
717
1569
  rolling: scorecard.rolling,
718
1570
  reactions: scorecard.reactions,
719
- receiptPath: receiptPath2
1571
+ receiptPath: receiptPath2,
1572
+ diagnosisPath,
1573
+ diagnosisVerdict: diagnosis?.verdict ?? null
720
1574
  },
721
1575
  null,
722
1576
  2