@tradejs/cli 3.1.29-beta.290 → 3.1.29-beta.292

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.
@@ -42,8 +42,40 @@ var import_chalk = __toESM(require("chalk"));
42
42
 
43
43
  // src/lib/strategyRelease/researchRetention.ts
44
44
  var import_promises = __toESM(require("fs/promises"));
45
- var import_node_path2 = __toESM(require("path"));
45
+
46
+ // src/lib/researchMetricSnapshot.ts
46
47
  var import_node_crypto = require("crypto");
48
+ var import_node_zlib = require("zlib");
49
+ var MAX_BYTES = 256 * 1024 * 1024;
50
+ var hash = (bytes) => (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex");
51
+ var validatePermanentResearchMetrics = (value) => {
52
+ if (!value || typeof value !== "object") return;
53
+ for (const [key, child] of Object.entries(value)) {
54
+ if (["matchedPairs", "tradeRows", "allProjectedLeaves"].includes(key) && Array.isArray(child) && child.length)
55
+ throw new Error(`Temporary payload ${key} is not a permanent metric`);
56
+ if (key === "trades" && Array.isArray(child) && child.some(
57
+ (row) => row && typeof row === "object" && ("signalId" in row || "entryTimestamp" in row)
58
+ ))
59
+ throw new Error("Temporary trade records are not permanent metrics");
60
+ validatePermanentResearchMetrics(child);
61
+ }
62
+ };
63
+ var decodeResearchMetricSnapshot = (snapshot) => {
64
+ if (!snapshot || typeof snapshot !== "object" || !("schema" in snapshot) || snapshot.schema !== "tradejs-metric-snapshot/v1" || !("encoding" in snapshot) || snapshot.encoding !== "gzip+base64" || !("decodedBytes" in snapshot) || typeof snapshot.decodedBytes !== "number" || !Number.isSafeInteger(snapshot.decodedBytes) || snapshot.decodedBytes <= 0 || snapshot.decodedBytes > MAX_BYTES || !("sha256" in snapshot) || typeof snapshot.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(snapshot.sha256) || !("payload" in snapshot) || typeof snapshot.payload !== "string")
65
+ throw new Error("Invalid metric snapshot envelope");
66
+ const bytes = (0, import_node_zlib.gunzipSync)(Buffer.from(snapshot.payload, "base64"), {
67
+ maxOutputLength: snapshot.decodedBytes
68
+ });
69
+ if (bytes.length !== snapshot.decodedBytes || hash(bytes) !== snapshot.sha256)
70
+ throw new Error("Metric snapshot checksum or size mismatch");
71
+ const metrics = JSON.parse(bytes.toString("utf8"));
72
+ validatePermanentResearchMetrics(metrics);
73
+ return metrics;
74
+ };
75
+
76
+ // src/lib/strategyRelease/researchRetention.ts
77
+ var import_node_path2 = __toESM(require("path"));
78
+ var import_node_crypto2 = require("crypto");
47
79
  var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
48
80
 
49
81
  // src/lib/strategyRelease/researchFileConsumers.ts
@@ -235,7 +267,7 @@ var mapFiles = async (entries, visit) => {
235
267
  if (worker.status === "rejected") throw worker.reason;
236
268
  return results;
237
269
  };
238
- var fileSha256 = async (file, bytes) => bytes <= 1024 * 1024 ? (0, import_node_crypto.createHash)("sha256").update(await import_promises.default.readFile(file)).digest("hex") : (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(file);
270
+ var fileSha256 = async (file, bytes) => bytes <= 1024 * 1024 ? (0, import_node_crypto2.createHash)("sha256").update(await import_promises.default.readFile(file)).digest("hex") : (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(file);
239
271
  var inside = (root, file) => {
240
272
  const relative = import_node_path2.default.relative(root, file);
241
273
  return relative !== "" && !relative.startsWith(`..${import_node_path2.default.sep}`) && relative !== ".." && !import_node_path2.default.isAbsolute(relative);
@@ -279,13 +311,27 @@ var applyResearchRetention = async (options) => {
279
311
  const header = Buffer.alloc(4096);
280
312
  const { bytesRead } = await handle.read(header, 0, header.length, 0);
281
313
  const frontmatter = header.subarray(0, bytesRead).toString("utf8").match(/^---\r?\n([\s\S]*?)\r?\n---/);
282
- if (!frontmatter || !/^schema:\s*tradejs-research\/v1\s*$/m.test(frontmatter[1]) || !(retired ? /^reproduction:\s*(?:complete|partial|blocked|legacy-partial)\s*$/m.test(
314
+ if (!frontmatter || !/^schema:\s*tradejs-research\/v[12]\s*$/m.test(frontmatter[1]) || !(retired ? /^reproduction:\s*(?:complete|partial|blocked|legacy-partial)\s*$/m.test(
283
315
  frontmatter[1]
284
316
  ) : /^reproduction:\s*complete\s*$/m.test(frontmatter[1]))) {
285
317
  throw new Error(
286
318
  `Research note must have reproduction: complete: ${file}`
287
319
  );
288
320
  }
321
+ if (!retired && /^schema:\s*tradejs-research\/v2\s*$/m.test(frontmatter[1])) {
322
+ if ((await import_promises.default.stat(file)).size > 8 * 1024 * 1024)
323
+ throw new Error("Compact permanent note exceeds 8 MiB");
324
+ const content = await import_promises.default.readFile(file, "utf8");
325
+ const section = content.split("## Metrics snapshot (machine-readable)")[1]?.split("## Reported metrics")[0] ?? "";
326
+ const blocks = [
327
+ ...section.matchAll(/```json\r?\n([\s\S]*?)\r?\n```/g)
328
+ ];
329
+ if (blocks.length !== 1)
330
+ throw new Error(
331
+ "Complete v2 note needs one verified metric snapshot"
332
+ );
333
+ decodeResearchMetricSnapshot(JSON.parse(blocks[0][1]));
334
+ }
289
335
  } finally {
290
336
  await handle.close();
291
337
  }
@@ -369,7 +415,7 @@ var applyResearchRetention = async (options) => {
369
415
  };
370
416
 
371
417
  // src/lib/strategyRelease.ts
372
- var import_node_crypto2 = require("crypto");
418
+ var import_node_crypto3 = require("crypto");
373
419
  var import_promises2 = __toESM(require("fs/promises"));
374
420
  var import_node_path3 = __toESM(require("path"));
375
421
  var import_types2 = require("@tradejs/types");
@@ -1539,7 +1585,7 @@ var verifyCompletedCoreResearchBundle = async ({
1539
1585
  const artifactHashes = manifest.artifactHashes != null && typeof manifest.artifactHashes === "object" && !Array.isArray(manifest.artifactHashes) ? manifest.artifactHashes : null;
1540
1586
  const parsedSpec = JSON.parse(specBytes.toString("utf8"));
1541
1587
  assert2(
1542
- manifest.schema === "tradejs-core-research-manifest/v1" && manifest.status === "completed" && manifest.researchId === researchId && manifest.specSha256 === specSha256 && artifactHashes?.["result.json"] === resultSha256 && (0, import_node_crypto2.createHash)("sha256").update(canonicalStrategyReleaseJson(parsedSpec)).digest("hex") === specSha256,
1588
+ manifest.schema === "tradejs-core-research-manifest/v1" && manifest.status === "completed" && manifest.researchId === researchId && manifest.specSha256 === specSha256 && artifactHashes?.["result.json"] === resultSha256 && (0, import_node_crypto3.createHash)("sha256").update(canonicalStrategyReleaseJson(parsedSpec)).digest("hex") === specSha256,
1543
1589
  "core result is not bound by its completed core research manifest"
1544
1590
  );
1545
1591
  for (const [relativePath, expectedSha256] of Object.entries(
@@ -1587,12 +1633,18 @@ async function collectReleaseEvidenceReferences(references) {
1587
1633
  }
1588
1634
  const nested = (value) => value != null && typeof value === "object" && !Array.isArray(value) ? value : null;
1589
1635
  const array = (value) => Array.isArray(value) ? value : [];
1590
- 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";
1636
+ const semanticallyValid = reference.kind === "core_research" ? [
1637
+ "tradejs-core-research-result/v1",
1638
+ "tradejs-core-research-result/v2"
1639
+ ].includes(String(artifact.schema)) || 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";
1591
1640
  assert2(
1592
1641
  semanticallyValid,
1593
1642
  `evidence ${reference.artifactId} does not match ${reference.kind}`
1594
1643
  );
1595
- if (reference.kind === "core_research" && artifact.schema === "tradejs-core-research-result/v1") {
1644
+ if (reference.kind === "core_research" && [
1645
+ "tradejs-core-research-result/v1",
1646
+ "tradejs-core-research-result/v2"
1647
+ ].includes(String(artifact.schema))) {
1596
1648
  await verifyCompletedCoreResearchBundle({
1597
1649
  artifact,
1598
1650
  resultPath: reference.path,