@tradejs/cli 3.0.1 → 3.1.1
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/README.md +2 -0
- package/dist/cli.js +3707 -2983
- package/dist/lib/aiPocketSearch.js +10 -0
- package/dist/lib/aiTrainResearch.js +24 -15
- package/dist/lib/coreResearch.js +52 -20
- package/dist/lib/researchAgent.js +16 -2
- package/dist/lib/researchRoots.js +59 -0
- package/dist/lib/runtimeLineage.js +43 -19
- package/dist/lib/runtimeModeConfig.js +1 -1
- package/dist/lib/runtimeSignalsStorage.js +2 -1
- package/dist/lib/strategyGateFingerprint.js +26 -15
- package/dist/lib/strategyRelease.js +266 -253
- package/dist/scripts/agentRun.js +83 -52
- package/dist/scripts/aiPocketSearch.js +2269 -1972
- package/dist/scripts/aiTrain.js +24 -15
- package/dist/scripts/coreResearch.js +91 -30
- package/dist/scripts/replay.js +69 -27
- package/dist/scripts/replayRunner.js +69 -27
- package/dist/scripts/researchAuto.js +34 -13
- package/dist/scripts/runtimeConfig.js +290 -0
- package/dist/scripts/runtimeEvidence.js +2 -1
- package/dist/scripts/runtimeParity.js +1 -1
- package/dist/scripts/runtimeScorecard.js +108 -103
- package/dist/scripts/signals.js +162 -224
- package/dist/scripts/signalsDaemon.js +162 -224
- package/dist/scripts/strategyRelease.js +222 -209
- package/dist/scripts/test-ml.js +8 -2
- package/package.json +13 -10
|
@@ -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
|
|
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
|
|
420
|
+
function assert2(condition, message) {
|
|
197
421
|
if (!condition) throw new Error(`Invalid strategy release: ${message}`);
|
|
198
422
|
}
|
|
199
|
-
var assertFingerprint = (value, label) =>
|
|
200
|
-
var assertLineageFingerprint = (value, label) =>
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
431
|
-
|
|
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
|
-
|
|
659
|
+
assert2(
|
|
436
660
|
input.researchBudget.hypothesisFamilies <= 3,
|
|
437
661
|
"at most 3 hypothesis families are allowed"
|
|
438
662
|
);
|
|
439
|
-
|
|
663
|
+
assert2(
|
|
440
664
|
input.researchBudget.maximumVariantsPerFamily <= 5,
|
|
441
665
|
"at most 5 variants per family are allowed"
|
|
442
666
|
);
|
|
443
|
-
|
|
667
|
+
assert2(
|
|
444
668
|
input.researchBudget.isolatedLongFinalists <= 1,
|
|
445
669
|
"at most one isolated-long finalist is allowed"
|
|
446
670
|
);
|
|
447
|
-
|
|
671
|
+
assert2(
|
|
448
672
|
input.researchBudget.aiGateTuningRounds <= 1,
|
|
449
673
|
"at most one AI-gate tuning round is allowed"
|
|
450
674
|
);
|
|
451
|
-
|
|
452
|
-
|
|
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
|
-
|
|
457
|
-
|
|
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
|
-
|
|
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
|
-
|
|
691
|
+
assert2(
|
|
468
692
|
Number.isInteger(input.monitoring.minimumProspectiveClosedTrades) && input.monitoring.minimumProspectiveClosedTrades > 0,
|
|
469
693
|
"monitoring.minimumProspectiveClosedTrades must be positive"
|
|
470
694
|
);
|
|
471
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
815
|
+
assert2(
|
|
592
816
|
envelope?.schema === "tradejs-strategy-release-envelope/v1",
|
|
593
817
|
"release envelope schema mismatch"
|
|
594
818
|
);
|
|
595
|
-
|
|
819
|
+
assert2(
|
|
596
820
|
envelope.releaseId === envelope.manifest?.releaseId,
|
|
597
821
|
"releaseId mismatch"
|
|
598
822
|
);
|
|
599
|
-
|
|
823
|
+
assert2(
|
|
600
824
|
strategyReleaseSha256(envelope.manifest) === envelope.manifestSha256,
|
|
601
825
|
"release manifest checksum mismatch"
|
|
602
826
|
);
|
|
603
827
|
const manifest = envelope.manifest;
|
|
604
|
-
|
|
605
|
-
manifest.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
|
-
|
|
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
|
-
|
|
1218
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1294
|
+
assert2(
|
|
1282
1295
|
semanticallyValid,
|
|
1283
1296
|
`evidence ${reference.artifactId} does not match ${reference.kind}`
|
|
1284
1297
|
);
|