@tradejs/cli 2.0.14 → 2.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2916 -978
- package/dist/lib/aiPocketSearch.js +493 -24
- package/dist/lib/aiTrainResearch.js +19 -2
- package/dist/lib/marketContextPrepare.js +85 -8
- package/dist/lib/researchAgent.js +3 -30
- package/dist/lib/runtimeDebugEvidence.js +76 -1
- package/dist/lib/runtimeEvidenceArtifacts.js +255 -0
- package/dist/lib/runtimeEvidenceSync.js +308 -0
- package/dist/lib/runtimeRedis.js +25 -0
- package/dist/lib/runtimeScorecard.js +442 -0
- package/dist/lib/runtimeTradeSync.js +9 -0
- package/dist/scripts/agentRun.js +16 -31
- package/dist/scripts/aiPocketSearch.js +717 -29
- package/dist/scripts/aiTrain.js +32 -2
- package/dist/scripts/backtest.js +83 -8
- package/dist/scripts/replay.js +92 -8
- package/dist/scripts/replayRunner.js +92 -8
- package/dist/scripts/replayRuntimeEvidence.js +68 -161
- package/dist/scripts/runtimeEvidence.js +316 -161
- package/dist/scripts/runtimeEvidenceSync.js +335 -0
- package/dist/scripts/runtimeParity.js +83 -8
- package/dist/scripts/runtimeScorecard.js +731 -0
- package/dist/scripts/signals.js +234 -142
- package/dist/scripts/signalsDaemon.js +234 -142
- package/dist/scripts/signalsSummary.js +9 -0
- package/dist/workers/testerWorker.js +2 -0
- package/package.json +11 -9
|
@@ -34,8 +34,8 @@ __export(runtimeEvidence_exports, {
|
|
|
34
34
|
runtimeEvidence: () => runtimeEvidence
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(runtimeEvidence_exports);
|
|
37
|
-
var
|
|
38
|
-
var
|
|
37
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
38
|
+
var import_node_path3 = __toESM(require("path"));
|
|
39
39
|
var import_args = __toESM(require("args"));
|
|
40
40
|
var import_chalk = __toESM(require("chalk"));
|
|
41
41
|
|
|
@@ -134,6 +134,29 @@ var loadRuntimeTrades = async (userName, {
|
|
|
134
134
|
const trades = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
|
|
135
135
|
return trades.filter(isRuntimeTradeRecord).filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
|
|
136
136
|
};
|
|
137
|
+
var loadRuntimeClosedTrades = async (userName, {
|
|
138
|
+
startTime,
|
|
139
|
+
endTime
|
|
140
|
+
}) => {
|
|
141
|
+
const dayKeys = (0, import_time.getRuntimeStorageDayKeys)(startTime, endTime);
|
|
142
|
+
const trades = (await Promise.all(
|
|
143
|
+
dayKeys.map(
|
|
144
|
+
(dayKey) => (0, import_redis.getHashJsonValues)(
|
|
145
|
+
import_redis.redisKeys.runtimeClosedTradeBucket(userName, dayKey)
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
)).flat();
|
|
149
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
150
|
+
for (const trade of trades) {
|
|
151
|
+
if (!isRuntimeTradeRecord(trade) || trade.status !== "closed" || !Number.isFinite(trade.exitTimestamp) || trade.exitTimestamp < startTime || trade.exitTimestamp >= endTime) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
deduped.set(trade.orderId, trade);
|
|
155
|
+
}
|
|
156
|
+
return [...deduped.values()].sort(
|
|
157
|
+
(left, right) => left.exitTimestamp - right.exitTimestamp
|
|
158
|
+
);
|
|
159
|
+
};
|
|
137
160
|
|
|
138
161
|
// src/lib/runtimeSignalsLoader.ts
|
|
139
162
|
var import_redis2 = require("@tradejs/infra/redis");
|
|
@@ -298,11 +321,6 @@ var formatRuntimeDebugDateTime = (timestamp) => new Intl.DateTimeFormat("ru-RU",
|
|
|
298
321
|
minute: "2-digit",
|
|
299
322
|
hour12: false
|
|
300
323
|
}).format(new Date(timestamp));
|
|
301
|
-
var buildRuntimeDebugEvaluationId = ({
|
|
302
|
-
strategy,
|
|
303
|
-
symbol,
|
|
304
|
-
timestamp
|
|
305
|
-
}) => `${strategy}:${symbol}:${timestamp}`;
|
|
306
324
|
var filterByStrategies = (values, strategies) => {
|
|
307
325
|
if (!strategies?.length) {
|
|
308
326
|
return values;
|
|
@@ -318,13 +336,30 @@ var collectRuntimeDebugEvidence = async ({
|
|
|
318
336
|
}) => {
|
|
319
337
|
const dayKeys = (0, import_time2.getRuntimeStorageDayKeys)(startTime, endTime);
|
|
320
338
|
const dayKeySet = new Set(dayKeys);
|
|
321
|
-
const [
|
|
339
|
+
const [
|
|
340
|
+
entryTrades,
|
|
341
|
+
closedTrades,
|
|
342
|
+
signals,
|
|
343
|
+
evaluations,
|
|
344
|
+
evaluationStatsBuckets,
|
|
345
|
+
configs
|
|
346
|
+
] = await Promise.all([
|
|
322
347
|
loadRuntimeTrades(userName, { startTime, endTime }),
|
|
348
|
+
loadRuntimeClosedTrades(userName, { startTime, endTime }),
|
|
323
349
|
loadRuntimeSignals(userName, { startTime, endTime }),
|
|
324
350
|
loadRuntimeSignalEvaluations(userName, { startTime, endTime }),
|
|
325
351
|
loadRuntimeSignalEvaluationStatsBuckets(userName),
|
|
326
352
|
loadRuntimeStrategyConfigs(userName)
|
|
327
353
|
]);
|
|
354
|
+
const tradesByOrderId = new Map(
|
|
355
|
+
entryTrades.map((trade) => [trade.orderId, trade])
|
|
356
|
+
);
|
|
357
|
+
for (const trade of closedTrades) {
|
|
358
|
+
tradesByOrderId.set(trade.orderId, trade);
|
|
359
|
+
}
|
|
360
|
+
const trades = [...tradesByOrderId.values()].sort(
|
|
361
|
+
(left, right) => left.entryTimestamp - right.entryTimestamp
|
|
362
|
+
);
|
|
328
363
|
const filteredStats = evaluationStatsBuckets.filter(
|
|
329
364
|
(entry) => dayKeySet.has(entry.dayKey) && (!strategies?.length || strategies.includes(entry.strategy))
|
|
330
365
|
);
|
|
@@ -339,7 +374,7 @@ var collectRuntimeDebugEvidence = async ({
|
|
|
339
374
|
)
|
|
340
375
|
};
|
|
341
376
|
};
|
|
342
|
-
var
|
|
377
|
+
var buildRuntimeEvidenceReportPayload = ({
|
|
343
378
|
userName,
|
|
344
379
|
startTime,
|
|
345
380
|
endTime,
|
|
@@ -348,160 +383,230 @@ var buildRuntimeDebugReportPayload = async ({
|
|
|
348
383
|
trades,
|
|
349
384
|
strategyConfigs,
|
|
350
385
|
evaluationStatsBuckets
|
|
351
|
-
}) => {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const evaluationValue = directEvaluationValue ?? bucketEvaluationValue ?? knownEvaluation ?? null;
|
|
416
|
-
return {
|
|
417
|
-
redisDebug: {
|
|
418
|
-
trade: tradeKey,
|
|
419
|
-
tradeBucket: {
|
|
420
|
-
key: tradeBucketKey,
|
|
421
|
-
field: trade.orderId
|
|
422
|
-
},
|
|
423
|
-
activeTrade: activeTradeKey,
|
|
424
|
-
signal: signalKey,
|
|
425
|
-
evaluation: {
|
|
426
|
-
key: evaluationBucketKey,
|
|
427
|
-
field: evaluationField,
|
|
428
|
-
directKey: evaluationKey
|
|
429
|
-
}
|
|
430
|
-
},
|
|
431
|
-
trade,
|
|
432
|
-
redisValues: {
|
|
433
|
-
trade: tradeValue,
|
|
434
|
-
tradeBucket: trade,
|
|
435
|
-
activeTrade: activeTradeValue,
|
|
436
|
-
signal: signalValue ?? signal ?? null,
|
|
437
|
-
evaluation: evaluationValue ?? knownEvaluation ?? null
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
})
|
|
441
|
-
);
|
|
442
|
-
const linkedEvaluations = /* @__PURE__ */ new Map();
|
|
443
|
-
for (const trade of debugTrades) {
|
|
444
|
-
const evaluation = trade.redisValues.evaluation;
|
|
445
|
-
if (evaluation && typeof evaluation === "object" && "evaluationId" in evaluation && typeof evaluation.evaluationId === "string") {
|
|
446
|
-
linkedEvaluations.set(
|
|
447
|
-
evaluation.evaluationId,
|
|
448
|
-
evaluation
|
|
449
|
-
);
|
|
450
|
-
}
|
|
386
|
+
}) => ({
|
|
387
|
+
reportType: "runtime-daily-evidence",
|
|
388
|
+
generatedAt: Date.now(),
|
|
389
|
+
userName,
|
|
390
|
+
window: {
|
|
391
|
+
startTime,
|
|
392
|
+
endTime,
|
|
393
|
+
startMsk: formatRuntimeDebugDateTime(startTime),
|
|
394
|
+
endMsk: formatRuntimeDebugDateTime(endTime),
|
|
395
|
+
dayKeys: (0, import_time2.getRuntimeStorageDayKeys)(startTime, endTime)
|
|
396
|
+
},
|
|
397
|
+
counts: {
|
|
398
|
+
trades: trades.length,
|
|
399
|
+
signals: signals.length,
|
|
400
|
+
evaluations: evaluations.length,
|
|
401
|
+
evaluationStatsBuckets: evaluationStatsBuckets?.length ?? 0,
|
|
402
|
+
strategyConfigs: strategyConfigs?.length ?? 0
|
|
403
|
+
},
|
|
404
|
+
trades: trades.map((trade) => ({ trade })),
|
|
405
|
+
signals: signals.map((signal) => ({ signal })),
|
|
406
|
+
evaluations: evaluations.map((evaluation) => ({ evaluation })),
|
|
407
|
+
evaluationStatsBuckets: evaluationStatsBuckets ?? [],
|
|
408
|
+
strategyConfigs: strategyConfigs ?? []
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// src/lib/runtimeEvidenceArtifacts.ts
|
|
412
|
+
var import_node_crypto = require("crypto");
|
|
413
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
414
|
+
var import_node_path = __toESM(require("path"));
|
|
415
|
+
var RUNTIME_EVIDENCE_MANIFEST_FILE = "manifest.json";
|
|
416
|
+
var RUNTIME_EVIDENCE_PAYLOAD_FILE = "runtime-evidence.json";
|
|
417
|
+
var RUNTIME_EVIDENCE_COMPLETE_FILE = ".complete";
|
|
418
|
+
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
419
|
+
var sha256 = (value) => (0, import_node_crypto.createHash)("sha256").update(value).digest("hex");
|
|
420
|
+
var isExistingDestinationError = (error) => {
|
|
421
|
+
const code = error.code;
|
|
422
|
+
return code === "EEXIST" || code === "ENOTEMPTY";
|
|
423
|
+
};
|
|
424
|
+
var safeSegment = (value, fallback) => {
|
|
425
|
+
const normalized = value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
426
|
+
return normalized && normalized !== "." && normalized !== ".." ? normalized : fallback;
|
|
427
|
+
};
|
|
428
|
+
var compactTimestamp = (timestamp) => new Date(timestamp).toISOString().replace(/[-:]/g, "").replace(/\.000Z$/, "Z");
|
|
429
|
+
var dateParts = (timestamp) => {
|
|
430
|
+
const date = new Date(timestamp);
|
|
431
|
+
return [
|
|
432
|
+
String(date.getUTCFullYear()),
|
|
433
|
+
String(date.getUTCMonth() + 1).padStart(2, "0"),
|
|
434
|
+
String(date.getUTCDate()).padStart(2, "0")
|
|
435
|
+
];
|
|
436
|
+
};
|
|
437
|
+
var assertFiniteTimestamp = (value, label) => {
|
|
438
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
439
|
+
throw new Error(`Invalid runtime evidence ${label}`);
|
|
440
|
+
}
|
|
441
|
+
return value;
|
|
442
|
+
};
|
|
443
|
+
var parseManifest = (value) => {
|
|
444
|
+
const manifest = asRecord(value);
|
|
445
|
+
const window = asRecord(manifest?.window);
|
|
446
|
+
const payload = asRecord(manifest?.payload);
|
|
447
|
+
const counts = asRecord(manifest?.counts);
|
|
448
|
+
if (manifest?.schemaVersion !== 1 || manifest.reportType !== "runtime-evidence" || typeof manifest.artifactId !== "string" || typeof manifest.deploymentId !== "string" || typeof manifest.userName !== "string" || typeof manifest.createdAt !== "number" || payload?.file !== RUNTIME_EVIDENCE_PAYLOAD_FILE || typeof payload.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(payload.sha256) || typeof payload.bytes !== "number" || !Array.isArray(manifest.lineageKeys)) {
|
|
449
|
+
throw new Error("Invalid runtime evidence manifest");
|
|
451
450
|
}
|
|
452
|
-
const reportEvaluations = evaluations.length > 0 ? evaluations : [...linkedEvaluations.values()];
|
|
453
451
|
return {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
452
|
+
schemaVersion: 1,
|
|
453
|
+
artifactId: manifest.artifactId,
|
|
454
|
+
reportType: "runtime-evidence",
|
|
455
|
+
deploymentId: manifest.deploymentId,
|
|
456
|
+
userName: manifest.userName,
|
|
457
|
+
createdAt: manifest.createdAt,
|
|
458
458
|
window: {
|
|
459
|
-
startTime,
|
|
460
|
-
endTime,
|
|
461
|
-
startMsk: formatRuntimeDebugDateTime(startTime),
|
|
462
|
-
endMsk: formatRuntimeDebugDateTime(endTime),
|
|
463
|
-
dayKeys
|
|
459
|
+
startTime: assertFiniteTimestamp(window?.startTime, "window.startTime"),
|
|
460
|
+
endTime: assertFiniteTimestamp(window?.endTime, "window.endTime")
|
|
464
461
|
},
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
runtimeSignals: import_redis3.redisKeys.runtimeSignalBuckets(userName),
|
|
470
|
-
runtimeSignalEvaluations: import_redis3.redisKeys.runtimeSignalEvaluationBuckets(userName),
|
|
471
|
-
runtimeSignalEvaluationStats: import_redis3.redisKeys.runtimeSignalEvaluationStatsBuckets(userName)
|
|
462
|
+
payload: {
|
|
463
|
+
file: RUNTIME_EVIDENCE_PAYLOAD_FILE,
|
|
464
|
+
sha256: payload.sha256,
|
|
465
|
+
bytes: payload.bytes
|
|
472
466
|
},
|
|
473
|
-
counts:
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
467
|
+
counts: Object.fromEntries(
|
|
468
|
+
Object.entries(counts ?? {}).filter(
|
|
469
|
+
(entry) => typeof entry[1] === "number" && Number.isFinite(entry[1])
|
|
470
|
+
)
|
|
471
|
+
),
|
|
472
|
+
lineageKeys: manifest.lineageKeys.filter(
|
|
473
|
+
(value2) => typeof value2 === "string" && value2.length > 0
|
|
474
|
+
)
|
|
475
|
+
};
|
|
476
|
+
};
|
|
477
|
+
var publishRuntimeEvidenceBundle = async ({
|
|
478
|
+
publishRoot,
|
|
479
|
+
deploymentId,
|
|
480
|
+
userName,
|
|
481
|
+
startTime,
|
|
482
|
+
endTime,
|
|
483
|
+
artifact,
|
|
484
|
+
counts,
|
|
485
|
+
lineageKeys
|
|
486
|
+
}) => {
|
|
487
|
+
const payload = `${JSON.stringify(artifact, null, 2)}
|
|
488
|
+
`;
|
|
489
|
+
const payloadSha256 = sha256(payload);
|
|
490
|
+
const safeDeploymentId = safeSegment(deploymentId, "default");
|
|
491
|
+
const artifactId = [
|
|
492
|
+
compactTimestamp(startTime),
|
|
493
|
+
compactTimestamp(endTime),
|
|
494
|
+
payloadSha256.slice(0, 16)
|
|
495
|
+
].join("_");
|
|
496
|
+
const createdAt = Date.now();
|
|
497
|
+
const manifest = {
|
|
498
|
+
schemaVersion: 1,
|
|
499
|
+
artifactId,
|
|
500
|
+
reportType: "runtime-evidence",
|
|
501
|
+
deploymentId: safeDeploymentId,
|
|
502
|
+
userName,
|
|
503
|
+
createdAt,
|
|
504
|
+
window: { startTime, endTime },
|
|
505
|
+
payload: {
|
|
506
|
+
file: RUNTIME_EVIDENCE_PAYLOAD_FILE,
|
|
507
|
+
sha256: payloadSha256,
|
|
508
|
+
bytes: Buffer.byteLength(payload)
|
|
479
509
|
},
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
510
|
+
counts,
|
|
511
|
+
lineageKeys: [...new Set(lineageKeys)].sort()
|
|
512
|
+
};
|
|
513
|
+
const incomingRoot = import_node_path.default.join(publishRoot, "incoming");
|
|
514
|
+
const readyRoot = import_node_path.default.join(
|
|
515
|
+
publishRoot,
|
|
516
|
+
"ready",
|
|
517
|
+
safeDeploymentId,
|
|
518
|
+
...dateParts(endTime)
|
|
519
|
+
);
|
|
520
|
+
const bundleDir = import_node_path.default.join(readyRoot, artifactId);
|
|
521
|
+
const temporaryDir = import_node_path.default.join(
|
|
522
|
+
incomingRoot,
|
|
523
|
+
`${artifactId}.tmp-${(0, import_node_crypto.randomUUID)()}`
|
|
524
|
+
);
|
|
525
|
+
await import_promises.default.mkdir(temporaryDir, { recursive: true });
|
|
526
|
+
try {
|
|
527
|
+
await Promise.all([
|
|
528
|
+
import_promises.default.writeFile(
|
|
529
|
+
import_node_path.default.join(temporaryDir, RUNTIME_EVIDENCE_PAYLOAD_FILE),
|
|
530
|
+
payload,
|
|
531
|
+
"utf8"
|
|
532
|
+
),
|
|
533
|
+
import_promises.default.writeFile(
|
|
534
|
+
import_node_path.default.join(temporaryDir, RUNTIME_EVIDENCE_MANIFEST_FILE),
|
|
535
|
+
`${JSON.stringify(manifest, null, 2)}
|
|
536
|
+
`,
|
|
537
|
+
"utf8"
|
|
538
|
+
)
|
|
539
|
+
]);
|
|
540
|
+
await import_promises.default.writeFile(
|
|
541
|
+
import_node_path.default.join(temporaryDir, RUNTIME_EVIDENCE_COMPLETE_FILE),
|
|
542
|
+
`${artifactId}
|
|
543
|
+
`,
|
|
544
|
+
"utf8"
|
|
545
|
+
);
|
|
546
|
+
await import_promises.default.mkdir(readyRoot, { recursive: true });
|
|
547
|
+
try {
|
|
548
|
+
await import_promises.default.rename(temporaryDir, bundleDir);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
if (!isExistingDestinationError(error)) {
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
553
|
+
await import_promises.default.rm(temporaryDir, { recursive: true, force: true });
|
|
554
|
+
}
|
|
555
|
+
} catch (error) {
|
|
556
|
+
await import_promises.default.rm(temporaryDir, { recursive: true, force: true });
|
|
557
|
+
throw error;
|
|
558
|
+
}
|
|
559
|
+
return verifyRuntimeEvidenceBundle(bundleDir);
|
|
560
|
+
};
|
|
561
|
+
var verifyRuntimeEvidenceBundle = async (bundleDir) => {
|
|
562
|
+
const manifestPath = import_node_path.default.join(bundleDir, RUNTIME_EVIDENCE_MANIFEST_FILE);
|
|
563
|
+
const payloadPath = import_node_path.default.join(bundleDir, RUNTIME_EVIDENCE_PAYLOAD_FILE);
|
|
564
|
+
const completePath = import_node_path.default.join(bundleDir, RUNTIME_EVIDENCE_COMPLETE_FILE);
|
|
565
|
+
const [manifestText, payload, completeText] = await Promise.all([
|
|
566
|
+
import_promises.default.readFile(manifestPath, "utf8"),
|
|
567
|
+
import_promises.default.readFile(payloadPath),
|
|
568
|
+
import_promises.default.readFile(completePath, "utf8")
|
|
569
|
+
]);
|
|
570
|
+
const manifest = parseManifest(JSON.parse(manifestText));
|
|
571
|
+
if (completeText.trim() !== manifest.artifactId) {
|
|
572
|
+
throw new Error(`Invalid completion marker in ${bundleDir}`);
|
|
573
|
+
}
|
|
574
|
+
if (payload.byteLength !== manifest.payload.bytes) {
|
|
575
|
+
throw new Error(`Runtime evidence size mismatch in ${bundleDir}`);
|
|
576
|
+
}
|
|
577
|
+
if (sha256(payload) !== manifest.payload.sha256) {
|
|
578
|
+
throw new Error(`Runtime evidence checksum mismatch in ${bundleDir}`);
|
|
579
|
+
}
|
|
580
|
+
const artifact = asRecord(JSON.parse(payload.toString("utf8")));
|
|
581
|
+
const artifactWindow = asRecord(artifact?.window);
|
|
582
|
+
if (artifact?.reportType !== "runtime-evidence" || artifactWindow?.startTime !== manifest.window.startTime || artifactWindow.endTime !== manifest.window.endTime) {
|
|
583
|
+
throw new Error(`Runtime evidence payload mismatch in ${bundleDir}`);
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
bundleDir,
|
|
587
|
+
manifestPath,
|
|
588
|
+
payloadPath,
|
|
589
|
+
manifest,
|
|
590
|
+
artifact
|
|
502
591
|
};
|
|
503
592
|
};
|
|
504
593
|
|
|
594
|
+
// src/lib/runtimeLineage.ts
|
|
595
|
+
var import_node_child_process = require("child_process");
|
|
596
|
+
var import_node_crypto2 = require("crypto");
|
|
597
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
598
|
+
var import_node_path2 = __toESM(require("path"));
|
|
599
|
+
var import_types = require("@tradejs/types");
|
|
600
|
+
var import_strategies = require("@tradejs/node/strategies");
|
|
601
|
+
var runtimeLineageKey = (lineage) => [
|
|
602
|
+
lineage.schemaVersion,
|
|
603
|
+
lineage.gitSha ?? "unknown",
|
|
604
|
+
lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
|
|
605
|
+
lineage.gateFingerprint,
|
|
606
|
+
lineage.configFingerprint,
|
|
607
|
+
lineage.contextFingerprint
|
|
608
|
+
].join(":");
|
|
609
|
+
|
|
505
610
|
// src/scripts/runtimeEvidence.ts
|
|
506
611
|
import_args.default.option(["u", "user"], "Use user config", "root");
|
|
507
612
|
import_args.default.option(
|
|
@@ -514,8 +619,22 @@ import_args.default.option(
|
|
|
514
619
|
"Fallback window in hours when start/end are omitted",
|
|
515
620
|
24
|
|
516
621
|
);
|
|
622
|
+
import_args.default.option(
|
|
623
|
+
"daily",
|
|
624
|
+
"Use the latest complete 21:00 MSK to 21:00 MSK window",
|
|
625
|
+
false
|
|
626
|
+
);
|
|
517
627
|
import_args.default.option(["s", "strategy"], "Strategy filter, comma-separated");
|
|
518
628
|
import_args.default.option(["o", "out"], "Output JSON path", "output/runtime-evidence.json");
|
|
629
|
+
import_args.default.option(
|
|
630
|
+
"publishDir",
|
|
631
|
+
"Publish an immutable bundle under <dir>/ready/<deployment>"
|
|
632
|
+
);
|
|
633
|
+
import_args.default.option(
|
|
634
|
+
"deployment",
|
|
635
|
+
"Deployment id used by immutable evidence bundles",
|
|
636
|
+
process.env.RUNTIME_EVIDENCE_DEPLOYMENT_ID || "production"
|
|
637
|
+
);
|
|
519
638
|
var flags = import_args.default.parse(process.argv);
|
|
520
639
|
var projectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
|
|
521
640
|
var toEpochMs = (value) => {
|
|
@@ -531,12 +650,28 @@ var toEpochMs = (value) => {
|
|
|
531
650
|
return Number.isFinite(parsed) ? parsed : null;
|
|
532
651
|
};
|
|
533
652
|
var resolveWindow = () => {
|
|
534
|
-
const
|
|
653
|
+
const explicitStartTime = toEpochMs(flags.startTime);
|
|
654
|
+
const explicitEndTime = toEpochMs(flags.endTime);
|
|
655
|
+
const now = Date.now();
|
|
656
|
+
const dailyEndTime = (() => {
|
|
657
|
+
if (!flags.daily || explicitStartTime != null || explicitEndTime != null) {
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
const date = new Date(now);
|
|
661
|
+
const boundary = Date.UTC(
|
|
662
|
+
date.getUTCFullYear(),
|
|
663
|
+
date.getUTCMonth(),
|
|
664
|
+
date.getUTCDate(),
|
|
665
|
+
18
|
|
666
|
+
);
|
|
667
|
+
return now >= boundary ? boundary : boundary - 24 * 60 * 60 * 1e3;
|
|
668
|
+
})();
|
|
669
|
+
const endTime = explicitEndTime ?? dailyEndTime ?? now;
|
|
535
670
|
const hours = Math.max(
|
|
536
671
|
1,
|
|
537
672
|
Number.parseInt(String(flags.hours ?? 24), 10) || 24
|
|
538
673
|
);
|
|
539
|
-
const startTime =
|
|
674
|
+
const startTime = explicitStartTime ?? endTime - hours * 60 * 60 * 1e3;
|
|
540
675
|
if (startTime >= endTime) {
|
|
541
676
|
throw new Error(
|
|
542
677
|
`Invalid window: startTime (${startTime}) must be less than endTime (${endTime})`
|
|
@@ -545,7 +680,7 @@ var resolveWindow = () => {
|
|
|
545
680
|
return {
|
|
546
681
|
startTime,
|
|
547
682
|
endTime,
|
|
548
|
-
source:
|
|
683
|
+
source: explicitStartTime != null || explicitEndTime != null ? "explicit" : dailyEndTime != null ? "daily" : "hours"
|
|
549
684
|
};
|
|
550
685
|
};
|
|
551
686
|
var parseStrategyFilter = (value) => String(value ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
@@ -558,7 +693,7 @@ var runtimeEvidence = async () => {
|
|
|
558
693
|
endTime,
|
|
559
694
|
strategies: strategies.length ? strategies : void 0
|
|
560
695
|
});
|
|
561
|
-
const runtime =
|
|
696
|
+
const runtime = buildRuntimeEvidenceReportPayload({
|
|
562
697
|
userName: flags.user,
|
|
563
698
|
startTime,
|
|
564
699
|
endTime,
|
|
@@ -582,11 +717,31 @@ var runtimeEvidence = async () => {
|
|
|
582
717
|
strategies,
|
|
583
718
|
runtime
|
|
584
719
|
};
|
|
585
|
-
const outPath =
|
|
586
|
-
await
|
|
587
|
-
await
|
|
720
|
+
const outPath = import_node_path3.default.resolve(projectRoot, String(flags.out));
|
|
721
|
+
await import_promises3.default.mkdir(import_node_path3.default.dirname(outPath), { recursive: true });
|
|
722
|
+
await import_promises3.default.writeFile(outPath, `${JSON.stringify(artifact, null, 2)}
|
|
588
723
|
`, "utf8");
|
|
724
|
+
const publishDir = String(flags.publishDir ?? "").trim();
|
|
725
|
+
const published = publishDir ? await publishRuntimeEvidenceBundle({
|
|
726
|
+
publishRoot: import_node_path3.default.resolve(projectRoot, publishDir),
|
|
727
|
+
deploymentId: String(flags.deployment),
|
|
728
|
+
userName: String(flags.user),
|
|
729
|
+
startTime,
|
|
730
|
+
endTime,
|
|
731
|
+
artifact,
|
|
732
|
+
counts: artifact.runtime.counts,
|
|
733
|
+
lineageKeys: [
|
|
734
|
+
...evidence.signals.map((signal) => signal.runtimeLineage),
|
|
735
|
+
...evidence.evaluations.map(
|
|
736
|
+
(evaluation) => evaluation.runtimeLineage
|
|
737
|
+
),
|
|
738
|
+
...evidence.trades.map((trade) => trade.runtimeLineage)
|
|
739
|
+
].filter((lineage) => lineage != null).map(runtimeLineageKey)
|
|
740
|
+
}) : null;
|
|
589
741
|
console.log(import_chalk.default.green(`Wrote ${outPath}`));
|
|
742
|
+
if (published) {
|
|
743
|
+
console.log(import_chalk.default.green(`Published ${published.bundleDir}`));
|
|
744
|
+
}
|
|
590
745
|
console.log(
|
|
591
746
|
JSON.stringify(
|
|
592
747
|
{
|