@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.
@@ -34,8 +34,8 @@ __export(runtimeEvidence_exports, {
34
34
  runtimeEvidence: () => runtimeEvidence
35
35
  });
36
36
  module.exports = __toCommonJS(runtimeEvidence_exports);
37
- var import_promises = __toESM(require("fs/promises"));
38
- var import_node_path = __toESM(require("path"));
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 [trades, signals, evaluations, evaluationStatsBuckets, configs] = await Promise.all([
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 buildRuntimeDebugReportPayload = async ({
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
- const signalById = new Map(
353
- signals.map((signal) => [signal.signalId, signal])
354
- );
355
- const evaluationById = new Map(
356
- evaluations.map((evaluation) => [evaluation.evaluationId, evaluation])
357
- );
358
- const evaluationBySignalShape = new Map(
359
- evaluations.map((evaluation) => [
360
- buildRuntimeDebugEvaluationId(evaluation),
361
- evaluation
362
- ])
363
- );
364
- const dayKeys = (0, import_time2.getRuntimeStorageDayKeys)(startTime, endTime);
365
- const debugTrades = await Promise.all(
366
- trades.map(async (trade) => {
367
- const signal = trade.signalId != null ? signalById.get(trade.signalId) : void 0;
368
- const evaluationId = buildRuntimeDebugEvaluationId({
369
- strategy: trade.strategy,
370
- symbol: trade.symbol,
371
- timestamp: signal?.timestamp ?? trade.entryTimestamp
372
- });
373
- const knownEvaluation = evaluationById.get(evaluationId) ?? evaluationBySignalShape.get(evaluationId);
374
- const tradeDayKey = (0, import_time2.getRuntimeStorageDayKey)(trade.entryTimestamp);
375
- const signalDayKey = signal != null ? (0, import_time2.getRuntimeStorageDayKey)(signal.timestamp) : tradeDayKey;
376
- const evaluationDayKey = knownEvaluation != null ? (0, import_time2.getRuntimeStorageDayKey)(knownEvaluation.timestamp) : signalDayKey;
377
- const tradeKey = import_redis3.redisKeys.runtimeTrade(userName, trade.orderId);
378
- const tradeBucketKey = import_redis3.redisKeys.runtimeTradeBucket(
379
- userName,
380
- tradeDayKey
381
- );
382
- const activeTradeKey = import_redis3.redisKeys.runtimeActiveTrade(
383
- userName,
384
- trade.symbol,
385
- trade.deploymentId ?? trade.accountId
386
- );
387
- const signalKey = trade.signalId != null ? import_redis3.redisKeys.storeSignal(trade.symbol, trade.signalId) : null;
388
- const evaluationBucketKey = import_redis3.redisKeys.runtimeSignalEvaluationBucket(
389
- userName,
390
- evaluationDayKey,
391
- knownEvaluation?.strategy ?? trade.strategy
392
- );
393
- const evaluationField = knownEvaluation?.evaluationId ?? evaluationId;
394
- const evaluationKey = import_redis3.redisKeys.runtimeSignalEvaluation(
395
- userName,
396
- evaluationField
397
- );
398
- const [
399
- tradeValue,
400
- activeTradeValue,
401
- signalValue,
402
- directEvaluationValue,
403
- bucketEvaluationValue
404
- ] = await Promise.all([
405
- (0, import_redis3.getData)(tradeKey, null),
406
- (0, import_redis3.getData)(activeTradeKey, null),
407
- signalKey ? (0, import_redis3.getData)(signalKey, null) : Promise.resolve(null),
408
- (0, import_redis3.getData)(evaluationKey, null),
409
- knownEvaluation ? Promise.resolve(knownEvaluation) : (0, import_redis3.getHashJsonField)(
410
- evaluationBucketKey,
411
- evaluationField,
412
- null
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
- reportType: "runtime-daily-debug",
455
- generatedAt: Date.now(),
456
- generatedAtMsk: formatRuntimeDebugDateTime(Date.now()),
457
- userName,
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
- redisPrefixes: {
466
- runtimeTrades: import_redis3.redisKeys.runtimeTrades(userName),
467
- runtimeTradeBuckets: import_redis3.redisKeys.runtimeTradeBuckets(userName),
468
- runtimeActiveTrades: import_redis3.redisKeys.runtimeActiveTrades(userName),
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
- trades: trades.length,
475
- signals: signals.length,
476
- evaluations: reportEvaluations.length,
477
- evaluationStatsBuckets: evaluationStatsBuckets?.length ?? 0,
478
- strategyConfigs: strategyConfigs?.length ?? 0
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
- trades: debugTrades,
481
- signals: signals.map((signal) => ({
482
- redisKey: import_redis3.redisKeys.storeSignal(signal.symbol, signal.signalId),
483
- signal
484
- })),
485
- evaluations: reportEvaluations.map((evaluation) => ({
486
- redisDebug: {
487
- key: import_redis3.redisKeys.runtimeSignalEvaluationBucket(
488
- userName,
489
- (0, import_time2.getRuntimeStorageDayKey)(evaluation.timestamp),
490
- evaluation.strategy
491
- ),
492
- field: evaluation.evaluationId,
493
- directKey: import_redis3.redisKeys.runtimeSignalEvaluation(
494
- userName,
495
- evaluation.evaluationId
496
- )
497
- },
498
- evaluation
499
- })),
500
- evaluationStatsBuckets: evaluationStatsBuckets ?? [],
501
- strategyConfigs: strategyConfigs ?? []
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 endTime = toEpochMs(flags.endTime) ?? Date.now();
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 = toEpochMs(flags.startTime) ?? endTime - hours * 60 * 60 * 1e3;
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: flags.startTime != null || flags.endTime != null ? "explicit" : "hours"
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 = await buildRuntimeDebugReportPayload({
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 = import_node_path.default.resolve(projectRoot, String(flags.out));
586
- await import_promises.default.mkdir(import_node_path.default.dirname(outPath), { recursive: true });
587
- await import_promises.default.writeFile(outPath, `${JSON.stringify(artifact, null, 2)}
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
  {