@tradejs/cli 2.0.19 → 2.0.21

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.
@@ -0,0 +1,1477 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/scripts/strategyRelease.ts
31
+ var strategyRelease_exports = {};
32
+ __export(strategyRelease_exports, {
33
+ main: () => main,
34
+ runStrategyReleaseCommand: () => runStrategyReleaseCommand
35
+ });
36
+ module.exports = __toCommonJS(strategyRelease_exports);
37
+ var import_promises2 = __toESM(require("fs/promises"));
38
+ var import_node_fs = require("fs");
39
+ var import_node_readline = require("readline");
40
+ var import_node_path2 = __toESM(require("path"));
41
+ var import_chalk = __toESM(require("chalk"));
42
+
43
+ // src/lib/strategyRelease.ts
44
+ var import_node_crypto = require("crypto");
45
+ var import_promises = __toESM(require("fs/promises"));
46
+ var import_node_path = __toESM(require("path"));
47
+ var import_types = require("@tradejs/types");
48
+ var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
49
+ var SHA256_RE = /^[a-f0-9]{64}$/;
50
+ var LINEAGE_FINGERPRINT_RE = /^[a-f0-9]{16}$/;
51
+ var DAY_MS = 864e5;
52
+ var REQUIRED_LONG_WINDOWS = [1095, 1460, 1825];
53
+ var REQUIRED_STABLE_TERMINAL_WINDOWS = [365, 180, 90];
54
+ var MIN_RECENT_DIRECTION_REPAIR_TRADES = 20;
55
+ var safeSegment = import_strategyReleaseEvidence.safeStrategyEvidenceSegment;
56
+ var compactTimestamp = import_strategyReleaseEvidence.compactStrategyEvidenceTimestamp;
57
+ var verifyFullPeriodChartArtifact = async (input) => {
58
+ const artifact = input.chartArtifact;
59
+ if (!artifact?.path || !SHA256_RE.test(artifact.sha256)) return false;
60
+ try {
61
+ const artifactPath = import_node_path.default.resolve(artifact.path);
62
+ if (await (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(artifactPath) !== artifact.sha256) {
63
+ return false;
64
+ }
65
+ const report = JSON.parse(await import_promises.default.readFile(artifactPath, "utf8"));
66
+ return Boolean(
67
+ report.chart?.persisted === true && report.chart.cardIds?.length && report.errors?.failed === 0 && report.run?.strategy === input.strategy && report.run.mode === "local-deterministic" && report.run.recent === 0 && report.run.since == null && report.run.until == null && Number(report.run.sourceRows) > 0
68
+ );
69
+ } catch {
70
+ return false;
71
+ }
72
+ };
73
+ var hasExactRuntimeTarget = (target) => Boolean(
74
+ target?.userName.trim() && target.deploymentId.trim() && target.accountId.trim() && target.strategyConfigName.trim()
75
+ );
76
+ var deriveStrategyReleaseResearchDecision = async (input) => {
77
+ const blockers = [];
78
+ const byDays = new Map(
79
+ input.historicalWindows.map((entry) => [entry.days, entry])
80
+ );
81
+ if (REQUIRED_LONG_WINDOWS.some((days) => !byDays.has(days))) {
82
+ blockers.push("HISTORICAL_MATRIX_INCOMPLETE");
83
+ }
84
+ if (REQUIRED_STABLE_TERMINAL_WINDOWS.some((days) => !byDays.has(days))) {
85
+ blockers.push("HISTORICAL_MATRIX_INCOMPLETE");
86
+ }
87
+ const longWindows = REQUIRED_LONG_WINDOWS.map(
88
+ (days) => byDays.get(days)
89
+ ).filter((entry) => Boolean(entry));
90
+ if (longWindows.some(
91
+ (entry) => entry.pnl <= 0 || entry.profitFactor < 1 || entry.long.pnl <= 0 || entry.long.profitFactor < 1 || entry.short.pnl <= 0 || entry.short.profitFactor < 1
92
+ )) {
93
+ blockers.push("HISTORICAL_EDGE_FAILED");
94
+ }
95
+ const stableTerminalWindows = REQUIRED_STABLE_TERMINAL_WINDOWS.map(
96
+ (days) => byDays.get(days)
97
+ ).filter((entry) => Boolean(entry));
98
+ if (stableTerminalWindows.some(
99
+ (entry) => entry.pnl <= 0 || entry.profitFactor < 1 || entry.long.pnl <= 0 || entry.long.profitFactor < 1 || entry.short.pnl <= 0 || entry.short.profitFactor < 1
100
+ )) {
101
+ blockers.push("HISTORICAL_EDGE_FAILED");
102
+ }
103
+ if (blockers.length) {
104
+ return {
105
+ strategy: input.strategy,
106
+ action: "STOP_RESEARCH",
107
+ repairAllowed: false,
108
+ targetDirection: input.recentFailure?.direction ?? null,
109
+ maxLossValue: null,
110
+ requiresRuntimeBinding: false,
111
+ blockers,
112
+ summary: "The final composition does not have complete positive ALL/LONG/SHORT evidence on the frozen 3y/4y/max-window matrix."
113
+ };
114
+ }
115
+ const repairAllowed = Boolean(
116
+ input.recentFailure && input.recentFailure.closedTrades >= MIN_RECENT_DIRECTION_REPAIR_TRADES && input.recentFailure.causalMechanismIdentified && input.recentFailure.repairRoundsUsed === 0 && !input.exposedEvaluation
117
+ );
118
+ if (repairAllowed) {
119
+ return {
120
+ strategy: input.strategy,
121
+ action: "REPAIR_RECENT_DIRECTION",
122
+ repairAllowed: true,
123
+ targetDirection: input.recentFailure.direction,
124
+ maxLossValue: null,
125
+ requiresRuntimeBinding: false,
126
+ blockers: [],
127
+ summary: "Spend the single terminal-direction repair round on the preregistered causal mechanism, then rebuild the full evidence matrix."
128
+ };
129
+ }
130
+ if (!input.candidateImplemented) blockers.push("CANDIDATE_NOT_IMPLEMENTED");
131
+ if (!await verifyFullPeriodChartArtifact(input)) {
132
+ blockers.push("FULL_PERIOD_CHART_MISSING");
133
+ }
134
+ if (input.forwardTest.maxLossValue !== 1) {
135
+ blockers.push("FORWARD_RISK_MUST_BE_ONE");
136
+ }
137
+ if (blockers.length) {
138
+ return {
139
+ strategy: input.strategy,
140
+ action: "FORWARD_BLOCKED",
141
+ repairAllowed: false,
142
+ targetDirection: input.recentFailure?.direction ?? null,
143
+ maxLossValue: null,
144
+ requiresRuntimeBinding: false,
145
+ blockers,
146
+ summary: "Complete the frozen candidate implementation and full-period chart before any micro-forward execution."
147
+ };
148
+ }
149
+ if (!input.forwardTest.authorized) {
150
+ return {
151
+ strategy: input.strategy,
152
+ action: "MICRO_FORWARD_READY",
153
+ repairAllowed: false,
154
+ targetDirection: input.recentFailure?.direction ?? null,
155
+ maxLossValue: 1,
156
+ requiresRuntimeBinding: !hasExactRuntimeTarget(
157
+ input.forwardTest.runtimeTarget
158
+ ),
159
+ blockers: ["FORWARD_NOT_AUTHORIZED"],
160
+ summary: "The candidate is ready for a separately authorized micro-forward test at MAX_LOSS_VALUE=1."
161
+ };
162
+ }
163
+ if (!hasExactRuntimeTarget(input.forwardTest.runtimeTarget)) {
164
+ return {
165
+ strategy: input.strategy,
166
+ action: "MICRO_FORWARD_READY",
167
+ repairAllowed: false,
168
+ targetDirection: input.recentFailure?.direction ?? null,
169
+ maxLossValue: 1,
170
+ requiresRuntimeBinding: true,
171
+ blockers: [],
172
+ summary: "The portable micro-forward handoff is ready. Bind it on the runtime server to that server's deployment/account ids and verify canonical composition fingerprints there."
173
+ };
174
+ }
175
+ return {
176
+ strategy: input.strategy,
177
+ action: "START_MICRO_FORWARD",
178
+ repairAllowed: false,
179
+ targetDirection: input.recentFailure?.direction ?? null,
180
+ maxLossValue: 1,
181
+ requiresRuntimeBinding: false,
182
+ blockers: [],
183
+ summary: "Start the exact frozen composition as a micro-forward test at MAX_LOSS_VALUE=1; do not retune an underpowered or exposed recent tail."
184
+ };
185
+ };
186
+ var canonicalStrategyReleaseJson = (value) => (0, import_strategyReleaseEvidence.canonicalStrategyEvidenceJson)(value);
187
+ var strategyReleaseSha256 = (value) => (0, import_strategyReleaseEvidence.strategyEvidenceSha256)(value);
188
+ function assert(condition, message) {
189
+ if (!condition) throw new Error(`Invalid strategy release: ${message}`);
190
+ }
191
+ var assertFingerprint = (value, label) => assert(SHA256_RE.test(value), `${label} must be a lowercase SHA-256`);
192
+ var assertLineageFingerprint = (value, label) => assert(
193
+ LINEAGE_FINGERPRINT_RE.test(value),
194
+ `${label} must be a 16-character lowercase lineage fingerprint`
195
+ );
196
+ var releaseReasons = (input) => {
197
+ const reasons = [];
198
+ const requiredEvidenceKinds = [
199
+ "core_research",
200
+ "ai_gate",
201
+ "runtime_parity",
202
+ "execution_calibration"
203
+ ];
204
+ if (input.evidence.some((entry) => !entry.verified) || requiredEvidenceKinds.some(
205
+ (kind) => !input.evidence.some((entry) => entry.kind === kind)
206
+ )) {
207
+ reasons.push("EVIDENCE_INCOMPLETE");
208
+ }
209
+ if (!input.gates.coreEdgeVerified) reasons.push("NO_VERIFIED_CORE_EDGE");
210
+ if (!input.gates.aiGateAddsValue) reasons.push("AI_GATE_ADDS_NO_VALUE");
211
+ if (!input.gates.currentMarketSuitable) {
212
+ reasons.push("CURRENT_REGIME_UNSUITABLE");
213
+ }
214
+ if (!input.gates.runtimeParityVerified) {
215
+ reasons.push("RUNTIME_PARITY_BLOCKED");
216
+ }
217
+ if (!input.gates.executionModelVerified) {
218
+ reasons.push("EXECUTION_MODEL_UNSAFE");
219
+ }
220
+ if (input.researchBudget.hypothesisFamilies >= 3 && !input.gates.coreEdgeVerified) {
221
+ reasons.push("RESEARCH_BUDGET_EXHAUSTED");
222
+ }
223
+ return [...new Set(reasons)];
224
+ };
225
+ var deriveReleaseGates = (evidence) => {
226
+ const asserted = (name) => {
227
+ const values = evidence.filter((entry) => entry.verified).map((entry) => entry.releaseAssertions?.[name]).filter((value) => typeof value === "boolean");
228
+ return values.length > 0 && values.every(Boolean);
229
+ };
230
+ return {
231
+ coreEdgeVerified: asserted("coreEdgeVerified"),
232
+ aiGateAddsValue: asserted("aiGateAddsValue"),
233
+ currentMarketSuitable: asserted("currentMarketSuitable"),
234
+ runtimeParityVerified: asserted("runtimeParityVerified"),
235
+ executionModelVerified: asserted("executionModelVerified")
236
+ };
237
+ };
238
+ var verifyEvidenceComposition = (strategy, composition, evidence) => {
239
+ const requireEqual = (actual, expected, label, artifactId) => assert(
240
+ actual === expected,
241
+ `evidence ${artifactId} ${label} does not match the frozen composition`
242
+ );
243
+ for (const reference of evidence) {
244
+ const lineage = reference.lineage;
245
+ if (reference.kind === "core_research") {
246
+ requireEqual(
247
+ lineage?.strategy,
248
+ strategy,
249
+ "strategy",
250
+ reference.artifactId
251
+ );
252
+ requireEqual(
253
+ lineage?.coreConfigSha256,
254
+ composition.coreConfigSha256,
255
+ "coreConfigSha256",
256
+ reference.artifactId
257
+ );
258
+ requireEqual(
259
+ lineage?.gitSha,
260
+ composition.gitSha,
261
+ "gitSha",
262
+ reference.artifactId
263
+ );
264
+ requireEqual(lineage?.gitDirty, false, "gitDirty", reference.artifactId);
265
+ requireEqual(
266
+ lineage?.maxLossValue,
267
+ composition.maxLossValue,
268
+ "MAX_LOSS_VALUE",
269
+ reference.artifactId
270
+ );
271
+ assert(
272
+ lineage?.sourceSha256s.includes(composition.coreExportSha256),
273
+ `evidence ${reference.artifactId} does not include the frozen core export`
274
+ );
275
+ continue;
276
+ }
277
+ if (reference.kind === "ai_gate") {
278
+ requireEqual(
279
+ lineage?.strategy,
280
+ strategy,
281
+ "strategy",
282
+ reference.artifactId
283
+ );
284
+ requireEqual(
285
+ lineage?.gitSha,
286
+ composition.gitSha,
287
+ "gitSha",
288
+ reference.artifactId
289
+ );
290
+ requireEqual(lineage?.gitDirty, false, "gitDirty", reference.artifactId);
291
+ requireEqual(
292
+ lineage?.gateConfigIdsFingerprint,
293
+ composition.gateConfigIdsFingerprint,
294
+ "gateConfigIdsFingerprint",
295
+ reference.artifactId
296
+ );
297
+ requireEqual(
298
+ lineage?.gateFingerprint,
299
+ composition.gateFingerprint,
300
+ "gateFingerprint",
301
+ reference.artifactId
302
+ );
303
+ requireEqual(
304
+ lineage?.gateContextFingerprint,
305
+ composition.gateContextFingerprint,
306
+ "gateContextFingerprint",
307
+ reference.artifactId
308
+ );
309
+ assert(
310
+ lineage?.sourceSha256s.includes(composition.coreExportSha256),
311
+ `evidence ${reference.artifactId} does not use the frozen core export`
312
+ );
313
+ continue;
314
+ }
315
+ if (reference.kind === "runtime_parity") {
316
+ requireEqual(
317
+ lineage?.strategy,
318
+ strategy,
319
+ "strategy",
320
+ reference.artifactId
321
+ );
322
+ requireEqual(
323
+ lineage?.gitSha,
324
+ composition.gitSha,
325
+ "gitSha",
326
+ reference.artifactId
327
+ );
328
+ requireEqual(lineage?.gitDirty, false, "gitDirty", reference.artifactId);
329
+ requireEqual(
330
+ lineage?.gateFingerprint,
331
+ composition.gateFingerprint,
332
+ "gateFingerprint",
333
+ reference.artifactId
334
+ );
335
+ requireEqual(
336
+ lineage?.runtimeConfigFingerprint,
337
+ composition.runtimeConfigFingerprint,
338
+ "runtimeConfigFingerprint",
339
+ reference.artifactId
340
+ );
341
+ requireEqual(
342
+ lineage?.runtimeContextFingerprint,
343
+ composition.runtimeContextFingerprint,
344
+ "runtimeContextFingerprint",
345
+ reference.artifactId
346
+ );
347
+ requireEqual(
348
+ lineage?.maxLossValue,
349
+ composition.maxLossValue,
350
+ "MAX_LOSS_VALUE",
351
+ reference.artifactId
352
+ );
353
+ continue;
354
+ }
355
+ if (reference.kind === "execution_calibration") {
356
+ requireEqual(
357
+ lineage?.strategy,
358
+ strategy,
359
+ "strategy",
360
+ reference.artifactId
361
+ );
362
+ requireEqual(
363
+ lineage?.gitSha,
364
+ composition.gitSha,
365
+ "gitSha",
366
+ reference.artifactId
367
+ );
368
+ requireEqual(lineage?.gitDirty, false, "gitDirty", reference.artifactId);
369
+ requireEqual(
370
+ lineage?.gateFingerprint,
371
+ composition.gateFingerprint,
372
+ "gateFingerprint",
373
+ reference.artifactId
374
+ );
375
+ requireEqual(
376
+ lineage?.runtimeConfigFingerprint,
377
+ composition.runtimeConfigFingerprint,
378
+ "runtimeConfigFingerprint",
379
+ reference.artifactId
380
+ );
381
+ requireEqual(
382
+ lineage?.runtimeContextFingerprint,
383
+ composition.runtimeContextFingerprint,
384
+ "runtimeContextFingerprint",
385
+ reference.artifactId
386
+ );
387
+ requireEqual(
388
+ lineage?.maxLossValue,
389
+ composition.maxLossValue,
390
+ "MAX_LOSS_VALUE",
391
+ reference.artifactId
392
+ );
393
+ }
394
+ }
395
+ };
396
+ var createStrategyReleaseManifest = (input) => {
397
+ assert(input.strategy.trim(), "strategy is required");
398
+ assert(
399
+ Number.isFinite(input.createdAt),
400
+ "createdAt must be a finite timestamp"
401
+ );
402
+ assert(
403
+ input.researchBudget.hypothesisFamilies <= 3,
404
+ "at most 3 hypothesis families are allowed"
405
+ );
406
+ assert(
407
+ input.researchBudget.maximumVariantsPerFamily <= 5,
408
+ "at most 5 variants per family are allowed"
409
+ );
410
+ assert(
411
+ input.researchBudget.isolatedLongFinalists <= 1,
412
+ "at most one isolated-long finalist is allowed"
413
+ );
414
+ assert(
415
+ input.researchBudget.aiGateTuningRounds <= 1,
416
+ "at most one AI-gate tuning round is allowed"
417
+ );
418
+ assert(input.marketWindow.cacheOnly, "market window must be cache-only");
419
+ assert(
420
+ input.marketWindow.endTime > input.marketWindow.startTime,
421
+ "market window is invalid"
422
+ );
423
+ assert(input.composition.maxLossValue > 0, "MAX_LOSS_VALUE must be positive");
424
+ assert(
425
+ input.composition.longEnabled && input.composition.shortEnabled,
426
+ "LONG and SHORT must both remain enabled"
427
+ );
428
+ assert(
429
+ Number.isInteger(input.monitoring.minimumProspectiveClosedTrades) && input.monitoring.minimumProspectiveClosedTrades > 0,
430
+ "monitoring.minimumProspectiveClosedTrades must be positive"
431
+ );
432
+ assert(
433
+ Number.isFinite(input.monitoring.minimumParityRatio) && input.monitoring.minimumParityRatio >= 0 && input.monitoring.minimumParityRatio <= 1,
434
+ "monitoring.minimumParityRatio must be between 0 and 1"
435
+ );
436
+ assert(
437
+ Number.isFinite(input.monitoring.maximumOrderFailureRate) && input.monitoring.maximumOrderFailureRate >= 0 && input.monitoring.maximumOrderFailureRate <= 1,
438
+ "monitoring.maximumOrderFailureRate must be between 0 and 1"
439
+ );
440
+ assert(
441
+ Number.isFinite(input.monitoring.minimumRegimeCoverage) && input.monitoring.minimumRegimeCoverage >= 0 && input.monitoring.minimumRegimeCoverage <= 1,
442
+ "monitoring.minimumRegimeCoverage must be between 0 and 1"
443
+ );
444
+ assert(
445
+ input.monitoring.drawdownEnvelopes.length > 0 && input.monitoring.drawdownEnvelopes.every(
446
+ (entry) => Number.isInteger(entry.days) && entry.days > 0 && Number.isFinite(entry.p95) && Number.isFinite(entry.maximum) && entry.p95 >= 0 && entry.maximum >= entry.p95
447
+ ),
448
+ "monitoring drawdown envelopes are invalid"
449
+ );
450
+ assertFingerprint(input.composition.coreConfigSha256, "coreConfigSha256");
451
+ assertFingerprint(input.composition.coreExportSha256, "coreExportSha256");
452
+ assertLineageFingerprint(
453
+ input.composition.gateConfigIdsFingerprint,
454
+ "gateConfigIdsFingerprint"
455
+ );
456
+ assertLineageFingerprint(
457
+ input.composition.runtimeConfigFingerprint,
458
+ "runtimeConfigFingerprint"
459
+ );
460
+ assertLineageFingerprint(
461
+ input.composition.gateFingerprint,
462
+ "gateFingerprint"
463
+ );
464
+ assertLineageFingerprint(
465
+ input.composition.gateContextFingerprint,
466
+ "gateContextFingerprint"
467
+ );
468
+ assertLineageFingerprint(
469
+ input.composition.runtimeContextFingerprint,
470
+ "runtimeContextFingerprint"
471
+ );
472
+ assertFingerprint(input.marketWindow.universeSha256, "universeSha256");
473
+ for (const entry of input.evidence) {
474
+ assertFingerprint(entry.sha256, `evidence ${entry.artifactId}`);
475
+ }
476
+ verifyEvidenceComposition(input.strategy, input.composition, input.evidence);
477
+ const evidenceGates = deriveReleaseGates(input.evidence);
478
+ assert(
479
+ canonicalStrategyReleaseJson(input.gates) === canonicalStrategyReleaseJson(evidenceGates),
480
+ "release gates must be derived from verified evidence assertions"
481
+ );
482
+ const compositionIdentity = {
483
+ strategy: input.strategy,
484
+ gitSha: input.composition.gitSha,
485
+ coreConfigSha256: input.composition.coreConfigSha256,
486
+ coreExportSha256: input.composition.coreExportSha256,
487
+ gateConfigIdsFingerprint: input.composition.gateConfigIdsFingerprint,
488
+ runtimeConfigFingerprint: input.composition.runtimeConfigFingerprint,
489
+ gateFingerprint: input.composition.gateFingerprint,
490
+ gateContextFingerprint: input.composition.gateContextFingerprint,
491
+ runtimeContextFingerprint: input.composition.runtimeContextFingerprint,
492
+ maxLossValue: input.composition.maxLossValue
493
+ };
494
+ const compositionId = `${safeSegment(input.strategy, "strategy")}_${strategyReleaseSha256(compositionIdentity).slice(0, 16)}`;
495
+ const reasons = releaseReasons({ ...input, gates: evidenceGates });
496
+ const economicFailure = reasons.some(
497
+ (reason) => [
498
+ "NO_VERIFIED_CORE_EDGE",
499
+ "AI_GATE_ADDS_NO_VALUE",
500
+ "CURRENT_REGIME_UNSUITABLE",
501
+ "RESEARCH_BUDGET_EXHAUSTED"
502
+ ].includes(reason)
503
+ );
504
+ const evidenceFailure = reasons.some(
505
+ (reason) => [
506
+ "EVIDENCE_INCOMPLETE",
507
+ "RUNTIME_PARITY_BLOCKED",
508
+ "EXECUTION_MODEL_UNSAFE"
509
+ ].includes(reason)
510
+ );
511
+ const verdict = evidenceFailure ? "INSUFFICIENT_EVIDENCE" : economicFailure ? "UNSUITABLE_FOR_CURRENT_MARKET" : "READY_FOR_RUNTIME";
512
+ const releaseIdentity = {
513
+ strategy: input.strategy,
514
+ createdAt: input.createdAt,
515
+ compositionId,
516
+ evidence: input.evidence.map(({ kind, artifactId, sha256 }) => ({
517
+ kind,
518
+ artifactId,
519
+ sha256
520
+ })),
521
+ verdict
522
+ };
523
+ return {
524
+ schema: import_types.STRATEGY_RELEASE_SCHEMA,
525
+ releaseId: `${safeSegment(input.strategy, "strategy")}_${compactTimestamp(input.createdAt)}_${strategyReleaseSha256(releaseIdentity).slice(0, 16)}`,
526
+ strategy: input.strategy,
527
+ createdAt: input.createdAt,
528
+ composition: { ...input.composition, compositionId },
529
+ marketWindow: input.marketWindow,
530
+ researchBudget: input.researchBudget,
531
+ evidence: input.evidence,
532
+ gates: evidenceGates,
533
+ monitoring: input.monitoring,
534
+ verdict: {
535
+ status: verdict,
536
+ reasons,
537
+ summary: input.summary
538
+ },
539
+ prospective: input.prospective
540
+ };
541
+ };
542
+ var createStrategyReleaseEnvelope = (manifest) => ({
543
+ schema: "tradejs-strategy-release-envelope/v1",
544
+ releaseId: manifest.releaseId,
545
+ manifestSha256: strategyReleaseSha256(manifest),
546
+ manifest
547
+ });
548
+ var verifyStrategyReleaseEnvelope = async (valueOrPath) => {
549
+ const value = typeof valueOrPath === "string" ? JSON.parse(await import_promises.default.readFile(valueOrPath, "utf8")) : valueOrPath;
550
+ const envelope = value;
551
+ assert(
552
+ envelope?.schema === "tradejs-strategy-release-envelope/v1",
553
+ "release envelope schema mismatch"
554
+ );
555
+ assert(
556
+ envelope.releaseId === envelope.manifest?.releaseId,
557
+ "releaseId mismatch"
558
+ );
559
+ assert(
560
+ strategyReleaseSha256(envelope.manifest) === envelope.manifestSha256,
561
+ "release manifest checksum mismatch"
562
+ );
563
+ const manifest = envelope.manifest;
564
+ assert(
565
+ manifest.schema === import_types.STRATEGY_RELEASE_SCHEMA,
566
+ "manifest schema mismatch"
567
+ );
568
+ const rebuilt = createStrategyReleaseManifest({
569
+ strategy: manifest.strategy,
570
+ createdAt: manifest.createdAt,
571
+ composition: {
572
+ gitSha: manifest.composition.gitSha,
573
+ coreConfigSha256: manifest.composition.coreConfigSha256,
574
+ coreExportSha256: manifest.composition.coreExportSha256,
575
+ gateConfigIdsFingerprint: manifest.composition.gateConfigIdsFingerprint,
576
+ runtimeConfigFingerprint: manifest.composition.runtimeConfigFingerprint,
577
+ gateFingerprint: manifest.composition.gateFingerprint,
578
+ gateContextFingerprint: manifest.composition.gateContextFingerprint,
579
+ runtimeContextFingerprint: manifest.composition.runtimeContextFingerprint,
580
+ maxLossValue: manifest.composition.maxLossValue,
581
+ longEnabled: manifest.composition.longEnabled,
582
+ shortEnabled: manifest.composition.shortEnabled
583
+ },
584
+ marketWindow: manifest.marketWindow,
585
+ researchBudget: manifest.researchBudget,
586
+ evidence: manifest.evidence,
587
+ gates: manifest.gates,
588
+ monitoring: manifest.monitoring,
589
+ summary: manifest.verdict.summary,
590
+ prospective: manifest.prospective
591
+ });
592
+ assert(
593
+ canonicalStrategyReleaseJson(rebuilt) === canonicalStrategyReleaseJson(manifest),
594
+ "release manifest contains self-declared or inconsistent derived fields"
595
+ );
596
+ return envelope;
597
+ };
598
+ var verifyStrategyEvidenceMarkerEnvelope = async (valueOrPath) => {
599
+ const value = typeof valueOrPath === "string" ? JSON.parse(await import_promises.default.readFile(valueOrPath, "utf8")) : valueOrPath;
600
+ return (0, import_strategyReleaseEvidence.verifyStrategyEvidenceMarkerEnvelope)(value);
601
+ };
602
+ var releaseMarkers = (manifest) => {
603
+ const common = {
604
+ timestamp: manifest.createdAt,
605
+ artifactId: manifest.releaseId,
606
+ artifactSha256: strategyReleaseSha256(manifest),
607
+ compositionId: manifest.composition.compositionId,
608
+ gitSha: manifest.composition.gitSha,
609
+ gateFingerprint: manifest.composition.gateFingerprint,
610
+ configFingerprint: manifest.composition.runtimeConfigFingerprint,
611
+ contextFingerprint: manifest.composition.runtimeContextFingerprint,
612
+ maxLossValue: manifest.composition.maxLossValue
613
+ };
614
+ return [
615
+ {
616
+ ...common,
617
+ id: `${manifest.releaseId}:evidence`,
618
+ type: "E",
619
+ label: "Evidence frozen",
620
+ summary: `${manifest.evidence.length} verified release artifacts`,
621
+ coverage: {
622
+ startTime: manifest.marketWindow.startTime,
623
+ endTime: manifest.marketWindow.endTime
624
+ }
625
+ },
626
+ {
627
+ ...common,
628
+ id: `${manifest.releaseId}:gate`,
629
+ type: "G",
630
+ label: "Composition frozen",
631
+ summary: manifest.composition.compositionId
632
+ },
633
+ {
634
+ ...common,
635
+ id: `${manifest.releaseId}:loss`,
636
+ type: "L",
637
+ label: "Trade loss value",
638
+ summary: `MAX_LOSS_VALUE ${manifest.composition.maxLossValue}`,
639
+ maxLossValue: manifest.composition.maxLossValue
640
+ },
641
+ {
642
+ ...common,
643
+ id: `${manifest.releaseId}:deployment`,
644
+ type: "D",
645
+ label: "Release manifest",
646
+ summary: manifest.verdict.status,
647
+ severity: manifest.verdict.status === "READY_FOR_RUNTIME" ? "info" : "blocking"
648
+ },
649
+ {
650
+ ...common,
651
+ id: `${manifest.releaseId}:recommendation`,
652
+ type: "R",
653
+ label: "Release recommendation",
654
+ summary: manifest.verdict.summary,
655
+ severity: manifest.verdict.status === "READY_FOR_RUNTIME" ? "info" : "warning"
656
+ }
657
+ ];
658
+ };
659
+ var writeJsonAtomic = async (filePath, value) => {
660
+ const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
661
+ await import_promises.default.mkdir(import_node_path.default.dirname(filePath), { recursive: true });
662
+ await import_promises.default.writeFile(
663
+ temporaryPath,
664
+ `${canonicalStrategyReleaseJson(value)}
665
+ `,
666
+ "utf8"
667
+ );
668
+ await import_promises.default.rename(temporaryPath, filePath);
669
+ };
670
+ var publishStrategyRelease = async ({
671
+ rootDir,
672
+ manifest
673
+ }) => {
674
+ const envelope = createStrategyReleaseEnvelope(manifest);
675
+ const markerEnvelope = (0, import_strategyReleaseEvidence.createStrategyEvidenceMarkerEnvelope)({
676
+ strategy: manifest.strategy,
677
+ createdAt: manifest.createdAt,
678
+ markers: releaseMarkers(manifest),
679
+ sourceArtifacts: manifest.evidence.map((entry) => ({
680
+ artifactId: entry.artifactId,
681
+ sha256: entry.sha256,
682
+ path: entry.path
683
+ }))
684
+ });
685
+ const releasePath = import_node_path.default.join(
686
+ rootDir,
687
+ "releases",
688
+ safeSegment(manifest.strategy, "strategy"),
689
+ `${manifest.releaseId}.json`
690
+ );
691
+ const markerPath = import_node_path.default.join(
692
+ rootDir,
693
+ "markers",
694
+ safeSegment(manifest.strategy, "strategy"),
695
+ `${markerEnvelope.artifactId}.json`
696
+ );
697
+ await Promise.all([
698
+ writeJsonAtomic(releasePath, envelope),
699
+ writeJsonAtomic(markerPath, markerEnvelope)
700
+ ]);
701
+ await Promise.all([
702
+ verifyStrategyReleaseEnvelope(releasePath),
703
+ verifyStrategyEvidenceMarkerEnvelope(markerPath)
704
+ ]);
705
+ return { releasePath, markerPath, envelope, markerEnvelope };
706
+ };
707
+ var buildStrategyLiveDiagnosis = (input) => {
708
+ const minimumClosedTrades = input.minimumClosedTrades ?? 20;
709
+ const minimumParityRatio = input.minimumParityRatio ?? 0.95;
710
+ const maximumOrderFailureRate = input.maximumOrderFailureRate ?? 0.05;
711
+ const minimumRegimeCoverage = input.minimumRegimeCoverage ?? 0.5;
712
+ const hasExplicitRiskScale = input.riskScaleComparable != null;
713
+ const comparisonObservedDrawdown = hasExplicitRiskScale ? input.riskScaleComparable ? input.normalizedObservedDrawdown ?? null : null : input.observedDrawdown;
714
+ const runtimeDivergence = !input.lineageComparable || input.parityRatio != null && input.parityRatio < minimumParityRatio || input.orderFailureRate != null && input.orderFailureRate > maximumOrderFailureRate;
715
+ const insufficient = input.closedTrades < minimumClosedTrades || input.parityRatio == null || comparisonObservedDrawdown == null || input.historicalDrawdownP95 == null;
716
+ const expected = !runtimeDivergence && !insufficient && comparisonObservedDrawdown <= input.historicalDrawdownP95;
717
+ const verdict = runtimeDivergence ? "RUNTIME_DIVERGENCE" : insufficient ? "INSUFFICIENT_EVIDENCE" : expected ? "EXPECTED_DRAWDOWN" : "GENERALIZATION_FAILURE";
718
+ const attributionIncomplete = input.rawCoreExpectancyDelta == null || input.aiGateAddedValue == null || input.regimeCoverage == null;
719
+ const attributedVerdict = verdict === "GENERALIZATION_FAILURE" && attributionIncomplete ? "INSUFFICIENT_EVIDENCE" : verdict;
720
+ 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";
721
+ 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.";
722
+ const recommendations = attributedVerdict === "RUNTIME_DIVERGENCE" ? [
723
+ "Resolve lineage and replay parity mismatches before evaluating strategy economics.",
724
+ "Inspect order failures and execution residuals against the frozen execution model."
725
+ ] : attributedVerdict === "EXPECTED_DRAWDOWN" ? [
726
+ "Keep the frozen composition unchanged and continue prospective evidence collection."
727
+ ] : attributedVerdict === "GENERALIZATION_FAILURE" ? [
728
+ "Do not retune the live cohort; open a new immutable research lineage.",
729
+ "Compare raw-core decay, AI-gate added value, and causal regime coverage."
730
+ ] : [
731
+ 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."
732
+ ];
733
+ return {
734
+ schema: import_types.STRATEGY_LIVE_DIAGNOSIS_SCHEMA,
735
+ strategy: input.strategy,
736
+ compositionId: input.compositionId,
737
+ createdAt: input.createdAt,
738
+ verdict: attributedVerdict,
739
+ subtype,
740
+ confidence: runtimeDivergence || input.closedTrades >= 50 ? "high" : input.closedTrades >= 20 ? "medium" : "low",
741
+ evidence: {
742
+ lineageComparable: input.lineageComparable,
743
+ riskScaleComparable: input.riskScaleComparable ?? false,
744
+ releaseMaxLossValue: input.releaseMaxLossValue ?? null,
745
+ runtimeMaxLossValue: input.runtimeMaxLossValue ?? null,
746
+ riskScaleRatio: input.riskScaleRatio ?? null,
747
+ parityRatio: input.parityRatio,
748
+ orderFailureRate: input.orderFailureRate,
749
+ observedDrawdown: input.observedDrawdown,
750
+ normalizedObservedDrawdown: input.normalizedObservedDrawdown ?? null,
751
+ historicalDrawdownP95: input.historicalDrawdownP95,
752
+ historicalDrawdownMaximum: input.historicalDrawdownMaximum,
753
+ closedTrades: input.closedTrades,
754
+ rawCoreExpectancyDelta: input.rawCoreExpectancyDelta,
755
+ aiGateAddedValue: input.aiGateAddedValue,
756
+ regimeCoverage: input.regimeCoverage,
757
+ overfitProbability: input.overfitProbability
758
+ },
759
+ explanation,
760
+ recommendations
761
+ };
762
+ };
763
+ var realizedDrawdown = (trades, startIndex = 0, endIndex = trades.length) => {
764
+ let equity = 0;
765
+ let peak = 0;
766
+ let maximum = 0;
767
+ for (let index = startIndex; index < endIndex; index += 1) {
768
+ const trade = trades[index];
769
+ equity += trade.netProfit;
770
+ peak = Math.max(peak, equity);
771
+ maximum = Math.max(maximum, peak - equity);
772
+ }
773
+ return maximum;
774
+ };
775
+ var lowerBoundExitTimestamp = (trades, timestamp) => {
776
+ let left = 0;
777
+ let right = trades.length;
778
+ while (left < right) {
779
+ const middle = Math.floor((left + right) / 2);
780
+ if (trades[middle].exitTimestamp < timestamp) left = middle + 1;
781
+ else right = middle;
782
+ }
783
+ return left;
784
+ };
785
+ var percentile = (values, probability) => {
786
+ const sorted = [...values].sort((left, right) => left - right);
787
+ const position = (sorted.length - 1) * probability;
788
+ const lower = Math.floor(position);
789
+ const upper = Math.ceil(position);
790
+ if (lower === upper) return sorted[lower];
791
+ return sorted[lower] * (upper - position) + sorted[upper] * (position - lower);
792
+ };
793
+ var buildStrategyMonitoringProfile = ({
794
+ trades,
795
+ startTime,
796
+ endTime,
797
+ days,
798
+ minimumProspectiveClosedTrades = 20,
799
+ minimumParityRatio = 0.95,
800
+ maximumOrderFailureRate = 0.05,
801
+ minimumRegimeCoverage = 0.5,
802
+ aiGateExpectancy = null,
803
+ overfitProbability = null
804
+ }) => {
805
+ assert(endTime > startTime, "monitoring profile window is invalid");
806
+ assert(days.length > 0, "monitoring profile days are required");
807
+ const ordered = trades.filter(
808
+ (trade) => Number.isFinite(trade.exitTimestamp) && Number.isFinite(trade.netProfit) && trade.exitTimestamp >= startTime && trade.exitTimestamp < endTime
809
+ ).sort((left, right) => left.exitTimestamp - right.exitTimestamp);
810
+ const drawdownEnvelopes = [...new Set(days)].sort((left, right) => left - right).map((windowDays) => {
811
+ assert(
812
+ Number.isInteger(windowDays) && windowDays > 0,
813
+ "monitoring profile days must be positive integers"
814
+ );
815
+ const duration = windowDays * DAY_MS;
816
+ assert(
817
+ endTime - startTime >= duration,
818
+ `${windowDays}d monitoring window exceeds historical coverage`
819
+ );
820
+ const drawdowns = [];
821
+ for (let windowStart = startTime; windowStart + duration <= endTime; windowStart += DAY_MS) {
822
+ const windowEnd = windowStart + duration;
823
+ drawdowns.push(
824
+ realizedDrawdown(
825
+ ordered,
826
+ lowerBoundExitTimestamp(ordered, windowStart),
827
+ lowerBoundExitTimestamp(ordered, windowEnd)
828
+ )
829
+ );
830
+ }
831
+ return {
832
+ days: windowDays,
833
+ p95: percentile(drawdowns, 0.95),
834
+ maximum: Math.max(...drawdowns)
835
+ };
836
+ });
837
+ return {
838
+ minimumProspectiveClosedTrades,
839
+ minimumParityRatio,
840
+ maximumOrderFailureRate,
841
+ minimumRegimeCoverage,
842
+ drawdownEnvelopes,
843
+ rawCoreExpectancy: ordered.length ? ordered.reduce((sum, trade) => sum + trade.netProfit, 0) / ordered.length : null,
844
+ aiGateExpectancy,
845
+ overfitProbability
846
+ };
847
+ };
848
+ var publishStrategyLiveDiagnosis = async ({
849
+ rootDir,
850
+ diagnosis,
851
+ sourceArtifacts,
852
+ composition
853
+ }) => {
854
+ const diagnosisSha256 = strategyReleaseSha256(diagnosis);
855
+ const diagnosisId = `${safeSegment(diagnosis.strategy, "strategy")}_${compactTimestamp(diagnosis.createdAt)}_${diagnosisSha256.slice(0, 16)}`;
856
+ const envelope = {
857
+ schema: "tradejs-strategy-live-diagnosis-envelope/v1",
858
+ diagnosisId,
859
+ diagnosisSha256,
860
+ diagnosis
861
+ };
862
+ const common = {
863
+ timestamp: diagnosis.createdAt,
864
+ artifactId: diagnosisId,
865
+ artifactSha256: diagnosisSha256,
866
+ compositionId: diagnosis.compositionId,
867
+ ...composition ? {
868
+ gitSha: composition.gitSha,
869
+ configFingerprint: composition.runtimeConfigFingerprint,
870
+ gateFingerprint: composition.gateFingerprint,
871
+ contextFingerprint: composition.runtimeContextFingerprint,
872
+ maxLossValue: composition.maxLossValue
873
+ } : {}
874
+ };
875
+ const markers = [
876
+ ...diagnosis.evidence.runtimeMaxLossValue != null && diagnosis.evidence.runtimeMaxLossValue !== diagnosis.evidence.releaseMaxLossValue ? [
877
+ {
878
+ ...common,
879
+ id: `${diagnosisId}:loss`,
880
+ type: "L",
881
+ label: "Trade loss value changed",
882
+ summary: `MAX_LOSS_VALUE ${diagnosis.evidence.runtimeMaxLossValue}`,
883
+ maxLossValue: diagnosis.evidence.runtimeMaxLossValue,
884
+ severity: "info"
885
+ }
886
+ ] : [],
887
+ ...diagnosis.verdict === "RUNTIME_DIVERGENCE" ? [
888
+ {
889
+ ...common,
890
+ id: `${diagnosisId}:parity`,
891
+ type: "P",
892
+ label: "Runtime parity mismatch",
893
+ summary: diagnosis.explanation,
894
+ severity: "blocking"
895
+ }
896
+ ] : [],
897
+ {
898
+ ...common,
899
+ id: `${diagnosisId}:recommendation`,
900
+ type: "R",
901
+ label: "Live diagnosis",
902
+ summary: `${diagnosis.verdict}: ${diagnosis.explanation}`,
903
+ severity: diagnosis.verdict === "EXPECTED_DRAWDOWN" ? "info" : "warning"
904
+ }
905
+ ];
906
+ const markerEnvelope = (0, import_strategyReleaseEvidence.createStrategyEvidenceMarkerEnvelope)({
907
+ strategy: diagnosis.strategy,
908
+ createdAt: diagnosis.createdAt,
909
+ markers,
910
+ sourceArtifacts
911
+ });
912
+ const diagnosisPath = import_node_path.default.join(
913
+ rootDir,
914
+ "diagnoses",
915
+ safeSegment(diagnosis.strategy, "strategy"),
916
+ `${diagnosisId}.json`
917
+ );
918
+ const markerPath = import_node_path.default.join(
919
+ rootDir,
920
+ "markers",
921
+ safeSegment(diagnosis.strategy, "strategy"),
922
+ `${markerEnvelope.artifactId}.json`
923
+ );
924
+ await Promise.all([
925
+ writeJsonAtomic(diagnosisPath, envelope),
926
+ writeJsonAtomic(markerPath, markerEnvelope)
927
+ ]);
928
+ await verifyStrategyEvidenceMarkerEnvelope(markerPath);
929
+ return { diagnosisPath, markerPath, envelope, markerEnvelope };
930
+ };
931
+ var planStrategyEvidenceRetention = ({
932
+ now,
933
+ entries,
934
+ retentionDays = {
935
+ operational_redis: 3,
936
+ verbose_payload: 14,
937
+ verified_runtime_bundle: 90,
938
+ compact_ledger: null
939
+ }
940
+ }) => {
941
+ const keep = [];
942
+ const remove = [];
943
+ for (const entry of entries) {
944
+ const days = retentionDays[entry.kind];
945
+ if (days == null || !entry.verified || !entry.aggregated || now - entry.createdAt <= days * DAY_MS) {
946
+ keep.push(entry);
947
+ } else {
948
+ remove.push(entry);
949
+ }
950
+ }
951
+ return {
952
+ keep,
953
+ delete: remove,
954
+ bytesReclaimable: remove.reduce((total, entry) => total + entry.bytes, 0)
955
+ };
956
+ };
957
+ var finiteString = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
958
+ var finiteNumber = (value) => {
959
+ const number = Number(value);
960
+ return Number.isFinite(number) ? number : null;
961
+ };
962
+ var uniqueStrings = (values) => [
963
+ ...new Set(
964
+ values.map(finiteString).filter((value) => value != null)
965
+ )
966
+ ];
967
+ var incompleteEvidenceLineage = () => ({
968
+ strategy: null,
969
+ gitSha: null,
970
+ gitDirty: null,
971
+ coreConfigSha256: null,
972
+ gateConfigIdsFingerprint: null,
973
+ gateFingerprint: null,
974
+ runtimeConfigFingerprint: null,
975
+ gateContextFingerprint: null,
976
+ runtimeContextFingerprint: null,
977
+ maxLossValue: null,
978
+ sourceSha256s: []
979
+ });
980
+ var extractEvidenceLineage = ({
981
+ artifact,
982
+ reference,
983
+ nested,
984
+ array
985
+ }) => {
986
+ const empty = incompleteEvidenceLineage();
987
+ if (reference.kind === "core_research") {
988
+ const variants = array(artifact.variants).map(nested).filter((entry) => entry != null);
989
+ const selectedCandidateIds = uniqueStrings(
990
+ array(artifact.comparisons).map(nested).filter(
991
+ (comparison) => comparison != null && nested(comparison.selection)?.passed === true
992
+ ).map((comparison) => comparison.candidateId)
993
+ );
994
+ const selectedVariants = selectedCandidateIds.length === 1 ? variants.filter(
995
+ (variant) => finiteString(nested(variant.variant)?.id) === selectedCandidateIds[0]
996
+ ) : [];
997
+ const configShas = uniqueStrings(
998
+ selectedVariants.map((variant) => nested(variant.variant)?.configSha256)
999
+ );
1000
+ const maxLossValues = [
1001
+ ...new Set(
1002
+ selectedVariants.map(
1003
+ (variant) => finiteNumber(
1004
+ nested(nested(variant.variant)?.resolvedConfig)?.MAX_LOSS_VALUE
1005
+ )
1006
+ ).filter((value) => value != null)
1007
+ )
1008
+ ];
1009
+ const sourceSha256s = uniqueStrings(
1010
+ selectedVariants.flatMap(
1011
+ (variant) => array(variant.files).map((file) => nested(file)?.sha256)
1012
+ )
1013
+ );
1014
+ const resultLineage = nested(artifact.lineage);
1015
+ const strategies = uniqueStrings(
1016
+ selectedVariants.map(
1017
+ (variant) => finiteString(nested(variant.variant)?.configName)?.split(":")[0]
1018
+ )
1019
+ );
1020
+ return {
1021
+ ...empty,
1022
+ strategy: strategies.length === 1 ? strategies[0] : null,
1023
+ gitSha: finiteString(resultLineage?.gitSha),
1024
+ gitDirty: resultLineage?.gitSha != null ? finiteString(resultLineage?.dirtyDiffSha256) != null : null,
1025
+ coreConfigSha256: configShas.length === 1 ? configShas[0] : null,
1026
+ maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null,
1027
+ sourceSha256s
1028
+ };
1029
+ }
1030
+ if (reference.kind === "ai_gate") {
1031
+ const lineage = nested(nested(artifact.research)?.lineage);
1032
+ return {
1033
+ ...empty,
1034
+ strategy: finiteString(nested(artifact.run)?.strategy),
1035
+ gitSha: finiteString(lineage?.gitSha),
1036
+ gitDirty: typeof lineage?.gitDirty === "boolean" ? lineage.gitDirty : null,
1037
+ gateConfigIdsFingerprint: finiteString(lineage?.configIdsFingerprint),
1038
+ gateFingerprint: finiteString(lineage?.gateFingerprint),
1039
+ gateContextFingerprint: finiteString(lineage?.contextFingerprint),
1040
+ sourceSha256s: uniqueStrings(array(lineage?.sourceSha256s))
1041
+ };
1042
+ }
1043
+ if (reference.kind === "runtime_parity") {
1044
+ const comparison = nested(nested(artifact.replay)?.runtimeComparison);
1045
+ const replayLineages = array(nested(comparison?.lineage)?.replay).map(nested).map((entry) => nested(entry?.lineage)).filter((entry) => entry != null);
1046
+ const one = (key) => {
1047
+ const values = uniqueStrings(replayLineages.map((entry) => entry[key]));
1048
+ return values.length === 1 ? values[0] : null;
1049
+ };
1050
+ const maxLossValues = [
1051
+ ...new Set(
1052
+ replayLineages.map((entry) => finiteNumber(entry.maxLossValue)).filter((value) => value != null)
1053
+ )
1054
+ ];
1055
+ const dirtyValues = [
1056
+ ...new Set(
1057
+ replayLineages.map((entry) => entry.gitDirty).filter((value) => typeof value === "boolean")
1058
+ )
1059
+ ];
1060
+ return {
1061
+ ...empty,
1062
+ strategy: (() => {
1063
+ const strategies = uniqueStrings(
1064
+ array(nested(comparison?.lineage)?.replay).map(
1065
+ (entry) => nested(entry)?.strategy
1066
+ )
1067
+ );
1068
+ return strategies.length === 1 ? strategies[0] : null;
1069
+ })(),
1070
+ gitSha: one("gitSha"),
1071
+ gitDirty: dirtyValues.length === 1 ? dirtyValues[0] : null,
1072
+ gateFingerprint: one("gateFingerprint"),
1073
+ runtimeConfigFingerprint: one("configFingerprint"),
1074
+ runtimeContextFingerprint: one("contextFingerprint"),
1075
+ maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null
1076
+ };
1077
+ }
1078
+ if (reference.kind === "execution_calibration") {
1079
+ const samples = [
1080
+ ...array(artifact.samples),
1081
+ ...array(artifact.replayOnlySamples)
1082
+ ].map(nested);
1083
+ const lineages = samples.map((sample) => nested(sample?.runtimeLineage)).filter((lineage) => lineage != null);
1084
+ const one = (key) => {
1085
+ const values = uniqueStrings(lineages.map((lineage) => lineage[key]));
1086
+ return values.length === 1 ? values[0] : null;
1087
+ };
1088
+ const dirtyValues = [
1089
+ ...new Set(
1090
+ lineages.map((lineage) => lineage.gitDirty).filter((value) => typeof value === "boolean")
1091
+ )
1092
+ ];
1093
+ const maxLossValues = [
1094
+ ...new Set(
1095
+ lineages.map((lineage) => finiteNumber(lineage.maxLossValue)).filter((value) => value != null)
1096
+ )
1097
+ ];
1098
+ return {
1099
+ ...empty,
1100
+ strategy: (() => {
1101
+ const strategies = uniqueStrings(
1102
+ samples.map((sample) => sample?.strategy)
1103
+ );
1104
+ return strategies.length === 1 ? strategies[0] : null;
1105
+ })(),
1106
+ gitSha: one("gitSha"),
1107
+ gitDirty: dirtyValues.length === 1 ? dirtyValues[0] : null,
1108
+ gateFingerprint: one("gateFingerprint"),
1109
+ runtimeConfigFingerprint: one("configFingerprint"),
1110
+ runtimeContextFingerprint: one("contextFingerprint"),
1111
+ maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null,
1112
+ sourceSha256s: uniqueStrings([
1113
+ ...array(nested(artifact.sources)?.sha256s),
1114
+ ...array(artifact.sourceSha256s)
1115
+ ])
1116
+ };
1117
+ }
1118
+ return empty;
1119
+ };
1120
+ var verifyCompletedCoreResearchBundle = async ({
1121
+ artifact,
1122
+ resultPath,
1123
+ resultSha256
1124
+ }) => {
1125
+ const researchId = finiteString(artifact.researchId);
1126
+ const specSha256 = finiteString(artifact.specSha256);
1127
+ assert(researchId != null, "core result researchId is required");
1128
+ assert(specSha256 != null, "core result specSha256 is required");
1129
+ const researchDir = import_node_path.default.dirname(resultPath);
1130
+ const manifestPath = import_node_path.default.join(researchDir, "manifest.json");
1131
+ const specPath = import_node_path.default.join(researchDir, "spec.json");
1132
+ const manifestBytes = await import_promises.default.readFile(manifestPath).catch(() => null);
1133
+ const specBytes = await import_promises.default.readFile(specPath).catch(() => null);
1134
+ assert(
1135
+ manifestBytes != null && specBytes != null,
1136
+ "core result must belong to a completed core research bundle"
1137
+ );
1138
+ const manifest = JSON.parse(manifestBytes.toString("utf8"));
1139
+ const artifactHashes = manifest.artifactHashes != null && typeof manifest.artifactHashes === "object" && !Array.isArray(manifest.artifactHashes) ? manifest.artifactHashes : null;
1140
+ const parsedSpec = JSON.parse(specBytes.toString("utf8"));
1141
+ assert(
1142
+ 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,
1143
+ "core result is not bound by its completed core research manifest"
1144
+ );
1145
+ for (const [relativePath, expectedSha256] of Object.entries(
1146
+ artifactHashes ?? {}
1147
+ )) {
1148
+ assert(
1149
+ !import_node_path.default.isAbsolute(relativePath) && !relativePath.split(/[\\/]/).includes(".."),
1150
+ `core bundle artifact ${relativePath} escapes its research directory`
1151
+ );
1152
+ assert(
1153
+ typeof expectedSha256 === "string" && SHA256_RE.test(expectedSha256),
1154
+ `core bundle artifact ${relativePath} has an invalid checksum`
1155
+ );
1156
+ const actualSha256 = await (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(
1157
+ import_node_path.default.join(researchDir, relativePath)
1158
+ );
1159
+ assert(
1160
+ actualSha256 === expectedSha256,
1161
+ `core bundle artifact ${relativePath} checksum mismatch`
1162
+ );
1163
+ }
1164
+ };
1165
+ async function collectReleaseEvidenceReferences(references) {
1166
+ return Promise.all(
1167
+ references.map(async (reference) => {
1168
+ const actualSha256 = await (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(reference.path);
1169
+ assert(
1170
+ actualSha256 === reference.sha256,
1171
+ `evidence ${reference.artifactId} checksum mismatch`
1172
+ );
1173
+ let artifact;
1174
+ try {
1175
+ const parsed = JSON.parse(
1176
+ await import_promises.default.readFile(reference.path, "utf8")
1177
+ );
1178
+ assert(
1179
+ parsed != null && typeof parsed === "object" && !Array.isArray(parsed),
1180
+ `evidence ${reference.artifactId} must be a JSON object`
1181
+ );
1182
+ artifact = parsed;
1183
+ } catch (error) {
1184
+ throw new Error(
1185
+ `Invalid strategy release: evidence ${reference.artifactId} is not valid JSON: ${error.message}`
1186
+ );
1187
+ }
1188
+ const nested = (value) => value != null && typeof value === "object" && !Array.isArray(value) ? value : null;
1189
+ const array = (value) => Array.isArray(value) ? value : [];
1190
+ 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";
1191
+ assert(
1192
+ semanticallyValid,
1193
+ `evidence ${reference.artifactId} does not match ${reference.kind}`
1194
+ );
1195
+ if (reference.kind === "core_research" && artifact.schema === "tradejs-core-research-result/v1") {
1196
+ await verifyCompletedCoreResearchBundle({
1197
+ artifact,
1198
+ resultPath: reference.path,
1199
+ resultSha256: actualSha256
1200
+ });
1201
+ }
1202
+ const releaseAssertions = (() => {
1203
+ if (reference.kind === "core_research") {
1204
+ const evidence = nested(artifact.evidence);
1205
+ const comparisons = array(artifact.comparisons).map(nested).filter((entry) => entry != null);
1206
+ const variants = array(artifact.variants).map(nested).filter((entry) => entry != null);
1207
+ const hasCohorts = (value) => {
1208
+ const cohorts = nested(value);
1209
+ return ["ALL", "LONG", "SHORT"].every(
1210
+ (cohort) => nested(cohorts?.[cohort]) != null
1211
+ );
1212
+ };
1213
+ const directionalEvidenceComplete = variants.length > 0 && variants.every(
1214
+ (variant) => hasCohorts(nested(variant.full)?.cohorts) && array(variant.terminal).every(
1215
+ (window) => hasCohorts(nested(window)?.cohorts)
1216
+ ) && array(variant.folds).every(
1217
+ (window) => hasCohorts(nested(window)?.cohorts)
1218
+ )
1219
+ );
1220
+ const evidenceComplete = [
1221
+ "terminals",
1222
+ "folds",
1223
+ "coldStart",
1224
+ "costStress",
1225
+ "delayStress",
1226
+ "fastNonFast",
1227
+ "runtimeParity"
1228
+ ].every((key) => evidence?.[key] === "present");
1229
+ const reconciled = variants.length > 0 && variants.every(
1230
+ (variant) => nested(variant.reconciliation)?.status === "match"
1231
+ );
1232
+ const selectedCandidateIds = uniqueStrings(
1233
+ comparisons.filter(
1234
+ (comparison) => nested(comparison.selection)?.passed === true
1235
+ ).map((comparison) => comparison.candidateId)
1236
+ );
1237
+ const selected = selectedCandidateIds.length === 1;
1238
+ const finalStage = artifact.stage === "isolated_long" || artifact.stage === "confirmation";
1239
+ const verified = finalStage && evidenceComplete && directionalEvidenceComplete && reconciled && selected;
1240
+ return {
1241
+ coreEdgeVerified: verified,
1242
+ currentMarketSuitable: verified
1243
+ };
1244
+ }
1245
+ if (reference.kind === "ai_gate") {
1246
+ const outcome = nested(artifact.outcome);
1247
+ const approvedRisk = nested(outcome?.approvedRisk);
1248
+ const terminalWindows = array(
1249
+ nested(artifact.research)?.terminalWindows
1250
+ ).map(nested).filter((entry) => entry != null);
1251
+ const hasLongShortDirections = (value) => {
1252
+ const directions = new Map(
1253
+ array(value).map(nested).filter(
1254
+ (entry) => entry != null
1255
+ ).map((entry) => [finiteString(entry.direction), entry])
1256
+ );
1257
+ return ["LONG", "SHORT"].every(
1258
+ (direction) => nested(directions.get(direction)?.summary) != null
1259
+ );
1260
+ };
1261
+ const directionalEvidenceComplete = hasLongShortDirections(artifact.byDirection) && terminalWindows.every(
1262
+ (window) => hasLongShortDirections(window.byDirection)
1263
+ );
1264
+ const terminalsPass = terminalWindows.length > 0 && terminalWindows.every((window) => {
1265
+ const terminalOutcome = nested(window.outcome);
1266
+ return window.complete === true && Number(terminalOutcome?.approved ?? 0) > 0 && Number(
1267
+ nested(terminalOutcome?.approvedRisk)?.totalProfit ?? Number.NEGATIVE_INFINITY
1268
+ ) > 0;
1269
+ });
1270
+ return {
1271
+ aiGateAddsValue: Number(outcome?.expectancyDelta ?? Number.NEGATIVE_INFINITY) > 0 && Number(approvedRisk?.totalProfit ?? Number.NEGATIVE_INFINITY) > 0 && Number(approvedRisk?.profitFactor ?? Number.NEGATIVE_INFINITY) > 1 && directionalEvidenceComplete && terminalsPass
1272
+ };
1273
+ }
1274
+ if (reference.kind === "runtime_parity") {
1275
+ const comparison = nested(nested(artifact.replay)?.runtimeComparison);
1276
+ const counts = nested(comparison?.counts);
1277
+ const matched = Number(counts?.matched ?? 0);
1278
+ const backtestOnly = Number(counts?.backtestOnly ?? 0);
1279
+ const runtimeOnly = Number(counts?.runtimeOnly ?? 0);
1280
+ return {
1281
+ runtimeParityVerified: matched > 0 && backtestOnly === 0 && runtimeOnly === 0 && nested(comparison?.lineage)?.reason == null
1282
+ };
1283
+ }
1284
+ if (reference.kind === "execution_calibration") {
1285
+ const counts = nested(artifact.counts);
1286
+ const all = nested(nested(artifact.summary)?.all);
1287
+ const residual = Number(
1288
+ nested(all?.residualVsCurrentModelBps)?.avg ?? Number.NaN
1289
+ );
1290
+ return {
1291
+ executionModelVerified: Number(counts?.fullTelemetryTrades ?? 0) > 0 && Number.isFinite(residual) && Math.abs(residual) <= 3
1292
+ };
1293
+ }
1294
+ return {};
1295
+ })();
1296
+ const lineage = extractEvidenceLineage({
1297
+ artifact,
1298
+ reference,
1299
+ nested,
1300
+ array
1301
+ });
1302
+ return { ...reference, verified: true, lineage, releaseAssertions };
1303
+ })
1304
+ );
1305
+ }
1306
+
1307
+ // src/scripts/strategyRelease.ts
1308
+ var valueAfter = (argv, name) => {
1309
+ const index = argv.indexOf(name);
1310
+ return index < 0 ? void 0 : argv[index + 1];
1311
+ };
1312
+ var required = (value, name) => {
1313
+ if (!value?.trim()) throw new Error(`${name} is required`);
1314
+ return value.trim();
1315
+ };
1316
+ var writeJsonAtomic2 = async (filePath, value) => {
1317
+ const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
1318
+ await import_promises2.default.mkdir(import_node_path2.default.dirname(filePath), { recursive: true });
1319
+ await import_promises2.default.writeFile(
1320
+ temporaryPath,
1321
+ `${JSON.stringify(value, null, 2)}
1322
+ `,
1323
+ "utf8"
1324
+ );
1325
+ await import_promises2.default.rename(temporaryPath, filePath);
1326
+ };
1327
+ var runStrategyReleaseCommand = async (options) => {
1328
+ const inputPath = import_node_path2.default.resolve(options.inputPath);
1329
+ if (options.command === "verify") {
1330
+ const envelope = await verifyStrategyReleaseEnvelope(inputPath);
1331
+ return {
1332
+ kind: "verified",
1333
+ releaseId: envelope.releaseId,
1334
+ verdict: envelope.manifest.verdict.status,
1335
+ inputPath
1336
+ };
1337
+ }
1338
+ if (options.command === "profile") {
1339
+ const startTime = options.startTime;
1340
+ const endTime = options.endTime;
1341
+ if (!Number.isFinite(startTime) || !Number.isFinite(endTime)) {
1342
+ throw new Error("--startTime and --endTime are required");
1343
+ }
1344
+ const trades = [];
1345
+ const lines = (0, import_node_readline.createInterface)({
1346
+ input: (0, import_node_fs.createReadStream)(inputPath, { encoding: "utf8" }),
1347
+ crlfDelay: Infinity
1348
+ });
1349
+ for await (const line of lines) {
1350
+ if (!line.trim()) continue;
1351
+ const row = JSON.parse(line);
1352
+ if (options.variantId && row.variantId !== options.variantId) continue;
1353
+ if (Number.isFinite(row.exitTimestamp) && Number.isFinite(row.netProfit)) {
1354
+ trades.push({
1355
+ exitTimestamp: row.exitTimestamp,
1356
+ netProfit: row.netProfit
1357
+ });
1358
+ }
1359
+ }
1360
+ const profile = buildStrategyMonitoringProfile({
1361
+ trades,
1362
+ startTime,
1363
+ endTime,
1364
+ days: options.days?.length ? options.days : [7, 30, 90]
1365
+ });
1366
+ const outputPath = import_node_path2.default.resolve(
1367
+ options.outputPath ?? "output/strategy-monitoring-profile.json"
1368
+ );
1369
+ await writeJsonAtomic2(outputPath, profile);
1370
+ return {
1371
+ kind: "profiled",
1372
+ trades: trades.length,
1373
+ outputPath,
1374
+ profile
1375
+ };
1376
+ }
1377
+ const input = JSON.parse(await import_promises2.default.readFile(inputPath, "utf8"));
1378
+ if (options.command === "decide") {
1379
+ const decision = await deriveStrategyReleaseResearchDecision(input);
1380
+ if (options.outputPath) {
1381
+ await writeJsonAtomic2(import_node_path2.default.resolve(options.outputPath), decision);
1382
+ }
1383
+ return {
1384
+ kind: "decided",
1385
+ ...decision,
1386
+ outputPath: options.outputPath ? import_node_path2.default.resolve(options.outputPath) : null
1387
+ };
1388
+ }
1389
+ if (options.command === "retention") {
1390
+ const plan = planStrategyEvidenceRetention(input);
1391
+ if (options.apply) {
1392
+ await Promise.all(plan.delete.map((entry) => import_promises2.default.rm(entry.path)));
1393
+ }
1394
+ return {
1395
+ kind: "retention",
1396
+ applied: Boolean(options.apply),
1397
+ keepCount: plan.keep.length,
1398
+ deleteCount: plan.delete.length,
1399
+ bytesReclaimable: plan.bytesReclaimable,
1400
+ delete: plan.delete.map((entry) => entry.path)
1401
+ };
1402
+ }
1403
+ if (options.command === "diagnose") {
1404
+ const diagnosis = buildStrategyLiveDiagnosis(input);
1405
+ const outputPath = import_node_path2.default.resolve(
1406
+ options.outputPath ?? "output/strategy-live-diagnosis.json"
1407
+ );
1408
+ await writeJsonAtomic2(outputPath, diagnosis);
1409
+ const published2 = options.rootDir ? await publishStrategyLiveDiagnosis({
1410
+ rootDir: import_node_path2.default.resolve(options.rootDir),
1411
+ diagnosis,
1412
+ sourceArtifacts: input.sourceArtifacts ?? []
1413
+ }) : null;
1414
+ return {
1415
+ kind: "diagnosed",
1416
+ verdict: diagnosis.verdict,
1417
+ outputPath,
1418
+ diagnosisPath: published2?.diagnosisPath ?? null,
1419
+ markerPath: published2?.markerPath ?? null
1420
+ };
1421
+ }
1422
+ const manifest = createStrategyReleaseManifest({
1423
+ ...input,
1424
+ evidence: await collectReleaseEvidenceReferences(input.evidence ?? [])
1425
+ });
1426
+ const published = await publishStrategyRelease({
1427
+ rootDir: import_node_path2.default.resolve(options.rootDir ?? "data/strategy-release"),
1428
+ manifest
1429
+ });
1430
+ return {
1431
+ kind: "created",
1432
+ releaseId: manifest.releaseId,
1433
+ verdict: manifest.verdict.status,
1434
+ releasePath: published.releasePath,
1435
+ markerPath: published.markerPath
1436
+ };
1437
+ };
1438
+ var printUsage = () => console.log(`Usage:
1439
+ yarn strategy:release create --input <draft.json> [--root data/strategy-release]
1440
+ yarn strategy:release verify --input <release.json>
1441
+ yarn strategy:release decide --input <research-decision.json> [--out <decision.json>]
1442
+ yarn strategy:release profile --input <trades.jsonl> --variant <id> --startTime <ms> --endTime <ms> [--days 7,30,90] [--out <profile.json>]
1443
+ yarn strategy:release diagnose --input <diagnosis-input.json> [--out output/diagnosis.json]
1444
+ yarn strategy:release retention --input <inventory.json> [--apply]`);
1445
+ var main = async () => {
1446
+ const argv = process.argv.slice(2);
1447
+ const command = argv[0];
1448
+ if (!command || ![
1449
+ "create",
1450
+ "verify",
1451
+ "diagnose",
1452
+ "decide",
1453
+ "profile",
1454
+ "retention"
1455
+ ].includes(command)) {
1456
+ printUsage();
1457
+ if (command) process.exitCode = 1;
1458
+ return;
1459
+ }
1460
+ const result = await runStrategyReleaseCommand({
1461
+ command,
1462
+ inputPath: required(valueAfter(argv, "--input"), "--input"),
1463
+ rootDir: valueAfter(argv, "--root"),
1464
+ outputPath: valueAfter(argv, "--out"),
1465
+ apply: argv.includes("--apply"),
1466
+ variantId: valueAfter(argv, "--variant"),
1467
+ startTime: Number(valueAfter(argv, "--startTime")),
1468
+ endTime: Number(valueAfter(argv, "--endTime")),
1469
+ days: valueAfter(argv, "--days")?.split(",").map((entry) => Number(entry.trim()))
1470
+ });
1471
+ console.log(import_chalk.default.green(JSON.stringify(result, null, 2)));
1472
+ };
1473
+ // Annotate the CommonJS export names for ESM import in node:
1474
+ 0 && (module.exports = {
1475
+ main,
1476
+ runStrategyReleaseCommand
1477
+ });