@tradejs/cli 3.1.29-beta.276 → 3.1.29-beta.277
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 +711 -241
- package/dist/lib/mcpReplayProcess.js +132 -0
- package/dist/lib/strategyRelease.js +18 -1
- package/dist/scripts/mcpWorker.js +155 -2
- package/dist/scripts/strategyRelease.js +408 -90
- package/package.json +7 -7
|
@@ -34,18 +34,346 @@ __export(strategyRelease_exports, {
|
|
|
34
34
|
runStrategyReleaseCommand: () => runStrategyReleaseCommand
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(strategyRelease_exports);
|
|
37
|
-
var
|
|
37
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
38
38
|
var import_node_fs = require("fs");
|
|
39
39
|
var import_node_readline = require("readline");
|
|
40
|
-
var
|
|
40
|
+
var import_node_path4 = __toESM(require("path"));
|
|
41
41
|
var import_chalk = __toESM(require("chalk"));
|
|
42
42
|
|
|
43
|
-
// src/lib/strategyRelease.ts
|
|
44
|
-
var import_node_crypto = require("crypto");
|
|
43
|
+
// src/lib/strategyRelease/researchRetention.ts
|
|
45
44
|
var import_promises = __toESM(require("fs/promises"));
|
|
45
|
+
var import_node_path2 = __toESM(require("path"));
|
|
46
|
+
var import_node_crypto = require("crypto");
|
|
47
|
+
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
48
|
+
|
|
49
|
+
// src/lib/strategyRelease/researchFileConsumers.ts
|
|
46
50
|
var import_node_path = __toESM(require("path"));
|
|
51
|
+
var import_node_child_process = require("child_process");
|
|
52
|
+
var import_node_util = require("util");
|
|
53
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
54
|
+
var runCommand = (command, args) => execFileAsync(command, args, { maxBuffer: 32 * 1024 * 1024 });
|
|
55
|
+
var GUEST_FILE_CHECK = `
|
|
56
|
+
import os, sys, json
|
|
57
|
+
if os.geteuid() != 0:
|
|
58
|
+
raise RuntimeError('Cannot inspect every container process')
|
|
59
|
+
wanted = set(json.loads(sys.argv[1]))
|
|
60
|
+
opened = set()
|
|
61
|
+
for pid in os.listdir('/proc'):
|
|
62
|
+
if not pid.isdigit():
|
|
63
|
+
continue
|
|
64
|
+
for kind in ('fd', 'map_files'):
|
|
65
|
+
directory = '/proc/' + pid + '/' + kind
|
|
66
|
+
try:
|
|
67
|
+
names = os.listdir(directory)
|
|
68
|
+
except FileNotFoundError:
|
|
69
|
+
continue
|
|
70
|
+
for name in names:
|
|
71
|
+
try:
|
|
72
|
+
target = os.readlink(directory + '/' + name)
|
|
73
|
+
except FileNotFoundError:
|
|
74
|
+
continue
|
|
75
|
+
if target in wanted:
|
|
76
|
+
opened.add(target)
|
|
77
|
+
print(json.dumps(sorted(opened)))
|
|
78
|
+
`;
|
|
79
|
+
var assertNoResearchFileConsumers = async (files, run = runCommand) => {
|
|
80
|
+
const virtualPids = /* @__PURE__ */ new Set();
|
|
81
|
+
const wanted = new Set(files.map((file) => import_node_path.default.resolve(file)));
|
|
82
|
+
let commonRoot = files.length ? import_node_path.default.dirname(files[0]) : "";
|
|
83
|
+
while (commonRoot && files.some((file) => {
|
|
84
|
+
const relative = import_node_path.default.relative(commonRoot, file);
|
|
85
|
+
return relative === ".." || relative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(relative);
|
|
86
|
+
}))
|
|
87
|
+
commonRoot = import_node_path.default.dirname(commonRoot);
|
|
88
|
+
const selectors = [];
|
|
89
|
+
if (files.length > 1e3 && commonRoot !== import_node_path.default.parse(commonRoot).root) {
|
|
90
|
+
selectors.push(["+D", commonRoot]);
|
|
91
|
+
} else {
|
|
92
|
+
for (let offset = 0; offset < files.length; offset += 100)
|
|
93
|
+
selectors.push(["--", ...files.slice(offset, offset + 100)]);
|
|
94
|
+
}
|
|
95
|
+
for (const selector of selectors) {
|
|
96
|
+
let output;
|
|
97
|
+
try {
|
|
98
|
+
output = (await run("lsof", ["-F", "pcn", ...selector])).stdout;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (error.code === 1) continue;
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
let pid = "";
|
|
104
|
+
let virtual = false;
|
|
105
|
+
for (const line of output.split("\n")) {
|
|
106
|
+
if (line.startsWith("p")) {
|
|
107
|
+
pid = line.slice(1);
|
|
108
|
+
virtual = false;
|
|
109
|
+
}
|
|
110
|
+
if (line.startsWith("c"))
|
|
111
|
+
virtual = line.startsWith("ccom.apple.Virtualization.");
|
|
112
|
+
if (line.startsWith("n")) {
|
|
113
|
+
if (!wanted.has(import_node_path.default.resolve(line.slice(1)))) continue;
|
|
114
|
+
if (!virtual || !/^\d+$/.test(pid))
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Research cleanup found open files: ${line.slice(1)}`
|
|
117
|
+
);
|
|
118
|
+
virtualPids.add(pid);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (!virtualPids.size) return;
|
|
123
|
+
for (const pid of virtualPids) {
|
|
124
|
+
const comm = (await run("ps", ["-p", pid, "-o", "comm="])).stdout.trim();
|
|
125
|
+
if (comm !== "/System/Library/Frameworks/Virtualization.framework/Versions/A/XPCServices/com.apple.Virtualization.VirtualMachine.xpc/Contents/MacOS/com.apple.Virtualization.VirtualMachine") {
|
|
126
|
+
throw new Error("Unrecognized virtual file consumer");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const contexts = JSON.parse(
|
|
130
|
+
(await run("docker", ["context", "inspect"])).stdout
|
|
131
|
+
);
|
|
132
|
+
if (contexts.length !== 1 || !contexts[0].Endpoints.docker.Host.startsWith("unix://")) {
|
|
133
|
+
throw new Error("Research file inspection requires a local Docker socket");
|
|
134
|
+
}
|
|
135
|
+
const ids = (await run("docker", ["ps", "-q"])).stdout.trim().split(/\s+/).filter(Boolean);
|
|
136
|
+
if (!ids.length)
|
|
137
|
+
throw new Error("Cannot resolve virtual research file handles");
|
|
138
|
+
const containers = JSON.parse(
|
|
139
|
+
(await run("docker", ["inspect", ...ids])).stdout
|
|
140
|
+
);
|
|
141
|
+
const covered = /* @__PURE__ */ new Set();
|
|
142
|
+
for (const container of containers) {
|
|
143
|
+
const guestFiles = /* @__PURE__ */ new Set();
|
|
144
|
+
for (const mount of container.Mounts) {
|
|
145
|
+
if (mount.Type !== "bind") continue;
|
|
146
|
+
for (const file of files) {
|
|
147
|
+
const relative = import_node_path.default.relative(mount.Source, file);
|
|
148
|
+
if (relative === "" || !relative.startsWith(`..${import_node_path.default.sep}`) && relative !== ".." && !import_node_path.default.isAbsolute(relative)) {
|
|
149
|
+
guestFiles.add(import_node_path.default.posix.join(mount.Destination, relative));
|
|
150
|
+
covered.add(file);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!guestFiles.size) continue;
|
|
155
|
+
const paths = [...guestFiles];
|
|
156
|
+
for (let offset = 0; offset < paths.length; offset += 250) {
|
|
157
|
+
const opened = JSON.parse(
|
|
158
|
+
(await run("docker", [
|
|
159
|
+
"exec",
|
|
160
|
+
container.Id,
|
|
161
|
+
"python",
|
|
162
|
+
"-c",
|
|
163
|
+
GUEST_FILE_CHECK,
|
|
164
|
+
JSON.stringify(paths.slice(offset, offset + 250))
|
|
165
|
+
])).stdout
|
|
166
|
+
);
|
|
167
|
+
if (!Array.isArray(opened) || opened.length)
|
|
168
|
+
throw new Error("Research cleanup found open files in a container");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (covered.size !== files.length)
|
|
172
|
+
throw new Error("Unresolved virtual research file handles");
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// src/lib/strategyRelease/evidenceRetention.ts
|
|
176
|
+
var DAY_MS = 864e5;
|
|
177
|
+
var isResearchRetired = (research) => research?.status === "retired" && research.retirement?.policy === "discard-unrecoverable-without-rerun" && Boolean(research.retirement.reason.trim()) && Boolean(research.retirement.missingEvidence.length) && research.retirement.missingEvidence.every((item) => Boolean(item.trim()));
|
|
178
|
+
var planStrategyEvidenceRetention = ({
|
|
179
|
+
now,
|
|
180
|
+
entries,
|
|
181
|
+
research,
|
|
182
|
+
retentionDays = {
|
|
183
|
+
operational_redis: 3,
|
|
184
|
+
verbose_payload: 14,
|
|
185
|
+
research_payload: 0,
|
|
186
|
+
research_cache: 0,
|
|
187
|
+
verified_runtime_bundle: 90,
|
|
188
|
+
compact_ledger: null
|
|
189
|
+
}
|
|
190
|
+
}) => {
|
|
191
|
+
const keep = [];
|
|
192
|
+
const remove = [];
|
|
193
|
+
const retired = isResearchRetired(research);
|
|
194
|
+
const roles = new Set(
|
|
195
|
+
research?.permanentArtifacts.map((artifact) => artifact.role)
|
|
196
|
+
);
|
|
197
|
+
const terminalResearch = Boolean(
|
|
198
|
+
(research?.status === "completed" || retired) && Number.isFinite(research?.completedAt) && research.completedAt <= now && (retired ? ["note"] : ["note", "candidates", "metrics"]).every(
|
|
199
|
+
(role) => roles.has(role)
|
|
200
|
+
)
|
|
201
|
+
);
|
|
202
|
+
for (const entry of entries) {
|
|
203
|
+
const days = retentionDays[entry.kind];
|
|
204
|
+
const isResearch = entry.kind === "research_payload" || entry.kind === "research_cache";
|
|
205
|
+
const completedResearch = Boolean(
|
|
206
|
+
terminalResearch && research?.researchId === entry.researchId && /^[a-f0-9]{64}$/.test(entry.sha256 ?? "")
|
|
207
|
+
);
|
|
208
|
+
if (isResearch && !completedResearch || days == null || !entry.verified || !entry.aggregated && !(isResearch && retired) || !isResearch && now - entry.createdAt <= days * DAY_MS) {
|
|
209
|
+
keep.push(entry);
|
|
210
|
+
} else {
|
|
211
|
+
remove.push(entry);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
keep,
|
|
216
|
+
delete: remove,
|
|
217
|
+
bytesReclaimable: remove.reduce((total, entry) => total + entry.bytes, 0)
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// src/lib/strategyRelease/researchRetention.ts
|
|
222
|
+
var SHA256_RE = /^[a-f0-9]{64}$/;
|
|
223
|
+
var mapFiles = async (entries, visit) => {
|
|
224
|
+
const results = new Array(entries.length);
|
|
225
|
+
let next = 0;
|
|
226
|
+
const workers = await Promise.allSettled(
|
|
227
|
+
Array.from({ length: Math.min(8, entries.length) }, async () => {
|
|
228
|
+
while (next < entries.length) {
|
|
229
|
+
const index = next++;
|
|
230
|
+
results[index] = await visit(entries[index]);
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
);
|
|
234
|
+
for (const worker of workers)
|
|
235
|
+
if (worker.status === "rejected") throw worker.reason;
|
|
236
|
+
return results;
|
|
237
|
+
};
|
|
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);
|
|
239
|
+
var inside = (root, file) => {
|
|
240
|
+
const relative = import_node_path2.default.relative(root, file);
|
|
241
|
+
return relative !== "" && !relative.startsWith(`..${import_node_path2.default.sep}`) && relative !== ".." && !import_node_path2.default.isAbsolute(relative);
|
|
242
|
+
};
|
|
243
|
+
var applyResearchRetention = async (options) => {
|
|
244
|
+
const projectRoot = await import_promises.default.realpath(options.projectRoot);
|
|
245
|
+
const dataRoot = import_node_path2.default.join(projectRoot, "data");
|
|
246
|
+
const resolve = (file) => import_node_path2.default.resolve(projectRoot, file);
|
|
247
|
+
const receiptPath = resolve(options.receiptPath);
|
|
248
|
+
const journalPath = `${receiptPath}.deleted.jsonl`;
|
|
249
|
+
const permanent = new Set(
|
|
250
|
+
options.research.permanentArtifacts.map((entry) => resolve(entry.path))
|
|
251
|
+
);
|
|
252
|
+
if (!inside(projectRoot, receiptPath) || permanent.has(receiptPath) || permanent.has(journalPath)) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
"Cleanup receipt must be separate from permanent research artifacts"
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
permanent.add(receiptPath);
|
|
258
|
+
permanent.add(journalPath);
|
|
259
|
+
for (const file of options.protectedPaths ?? []) permanent.add(resolve(file));
|
|
260
|
+
const retired = isResearchRetired(options.research);
|
|
261
|
+
if (options.research.status !== "completed" && !retired || !Number.isFinite(options.research.completedAt) || options.research.completedAt > Date.now()) {
|
|
262
|
+
throw new Error("Research must be completed before cleanup");
|
|
263
|
+
}
|
|
264
|
+
for (const role of retired ? ["note"] : ["note", "candidates", "metrics"]) {
|
|
265
|
+
if (!options.research.permanentArtifacts.some((entry) => entry.role === role)) {
|
|
266
|
+
throw new Error(`Missing permanent research ${role}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
await mapFiles(options.research.permanentArtifacts, async (artifact) => {
|
|
270
|
+
const file = resolve(artifact.path);
|
|
271
|
+
if (!inside(projectRoot, await import_promises.default.realpath(file)) || !SHA256_RE.test(artifact.sha256) || await fileSha256(file, (await import_promises.default.stat(file)).size) !== artifact.sha256) {
|
|
272
|
+
throw new Error(
|
|
273
|
+
`Permanent research artifact checksum/path mismatch: ${file}`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
if (artifact.role === "note") {
|
|
277
|
+
const handle = await import_promises.default.open(file, "r");
|
|
278
|
+
try {
|
|
279
|
+
const header = Buffer.alloc(4096);
|
|
280
|
+
const { bytesRead } = await handle.read(header, 0, header.length, 0);
|
|
281
|
+
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(
|
|
283
|
+
frontmatter[1]
|
|
284
|
+
) : /^reproduction:\s*complete\s*$/m.test(frontmatter[1]))) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
`Research note must have reproduction: complete: ${file}`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
} finally {
|
|
290
|
+
await handle.close();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
const checkedPaths = /* @__PURE__ */ new Set();
|
|
295
|
+
const checked = await mapFiles(options.entries, async (entry) => {
|
|
296
|
+
const file = resolve(entry.path);
|
|
297
|
+
if (!["research_payload", "research_cache"].includes(entry.kind) || entry.researchId !== options.research.researchId || !entry.verified || !entry.aggregated && !retired || !SHA256_RE.test(entry.sha256 ?? "") || !inside(dataRoot, file) || permanent.has(file) || /\.(?:md|patch|diff)$/i.test(file)) {
|
|
298
|
+
throw new Error(`Not a disposable research file: ${file}`);
|
|
299
|
+
}
|
|
300
|
+
if (checkedPaths.has(file))
|
|
301
|
+
throw new Error(`Duplicate cleanup path: ${file}`);
|
|
302
|
+
checkedPaths.add(file);
|
|
303
|
+
if (await import_promises.default.realpath(file) !== file)
|
|
304
|
+
throw new Error(`Research cleanup refuses symlinks: ${file}`);
|
|
305
|
+
const stat = await import_promises.default.lstat(file);
|
|
306
|
+
if (!stat.isFile() || stat.size !== entry.bytes || await fileSha256(file, stat.size) !== entry.sha256) {
|
|
307
|
+
throw new Error(
|
|
308
|
+
`Disposable research file checksum/size mismatch: ${file}`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
return { entry, file, mtimeMs: stat.mtimeMs, ino: stat.ino };
|
|
312
|
+
});
|
|
313
|
+
await assertNoResearchFileConsumers(checked.map((entry) => entry.file));
|
|
314
|
+
const receipt = {
|
|
315
|
+
schema: "tradejs-research-cleanup-receipt/v1",
|
|
316
|
+
researchId: options.research.researchId,
|
|
317
|
+
completedAt: options.research.completedAt,
|
|
318
|
+
...retired ? { retirement: options.research.retirement } : {},
|
|
319
|
+
journalPath,
|
|
320
|
+
permanentArtifacts: options.research.permanentArtifacts,
|
|
321
|
+
scope: "reported aggregates and frozen candidates; new trade-level analyses require a rerun",
|
|
322
|
+
status: "planned",
|
|
323
|
+
files: checked.map(({ entry, file }) => ({
|
|
324
|
+
path: file,
|
|
325
|
+
sha256: entry.sha256,
|
|
326
|
+
bytes: entry.bytes,
|
|
327
|
+
removed: false
|
|
328
|
+
}))
|
|
329
|
+
};
|
|
330
|
+
if (!options.apply) return receipt;
|
|
331
|
+
await import_promises.default.mkdir(import_node_path2.default.dirname(receiptPath), { recursive: true });
|
|
332
|
+
if (await import_promises.default.realpath(import_node_path2.default.dirname(receiptPath)) !== import_node_path2.default.dirname(receiptPath)) {
|
|
333
|
+
throw new Error("Cleanup receipt refuses symlinked directories");
|
|
334
|
+
}
|
|
335
|
+
const lock = await import_promises.default.open(`${receiptPath}.lock`, "wx");
|
|
336
|
+
let journal;
|
|
337
|
+
const writeReceipt = async () => {
|
|
338
|
+
const temp = `${receiptPath}.${process.pid}.tmp`;
|
|
339
|
+
await import_promises.default.writeFile(temp, `${JSON.stringify(receipt, null, 2)}
|
|
340
|
+
`, "utf8");
|
|
341
|
+
await import_promises.default.rename(temp, receiptPath);
|
|
342
|
+
};
|
|
343
|
+
try {
|
|
344
|
+
journal = await import_promises.default.open(journalPath, "wx");
|
|
345
|
+
receipt.status = "applying";
|
|
346
|
+
await writeReceipt();
|
|
347
|
+
for (let index = 0; index < checked.length; index += 1) {
|
|
348
|
+
const { file, entry, ino, mtimeMs } = checked[index];
|
|
349
|
+
const stat = await import_promises.default.lstat(file);
|
|
350
|
+
if (!stat.isFile() || stat.ino !== ino || stat.mtimeMs !== mtimeMs || stat.size !== entry.bytes) {
|
|
351
|
+
throw new Error(`Research file changed during cleanup: ${file}`);
|
|
352
|
+
}
|
|
353
|
+
await import_promises.default.unlink(file);
|
|
354
|
+
receipt.files[index].removed = true;
|
|
355
|
+
await journal.write(`${JSON.stringify(receipt.files[index])}
|
|
356
|
+
`);
|
|
357
|
+
}
|
|
358
|
+
receipt.status = "completed";
|
|
359
|
+
await writeReceipt();
|
|
360
|
+
return receipt;
|
|
361
|
+
} catch (error) {
|
|
362
|
+
if (journal) await writeReceipt();
|
|
363
|
+
throw error;
|
|
364
|
+
} finally {
|
|
365
|
+
await journal?.close();
|
|
366
|
+
await lock.close();
|
|
367
|
+
await import_promises.default.unlink(`${receiptPath}.lock`);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// src/lib/strategyRelease.ts
|
|
372
|
+
var import_node_crypto2 = require("crypto");
|
|
373
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
374
|
+
var import_node_path3 = __toESM(require("path"));
|
|
47
375
|
var import_types2 = require("@tradejs/types");
|
|
48
|
-
var
|
|
376
|
+
var import_strategyReleaseEvidence2 = require("@tradejs/infra/strategyReleaseEvidence");
|
|
49
377
|
|
|
50
378
|
// src/lib/strategyRelease/liveDiagnosis.ts
|
|
51
379
|
var import_types = require("@tradejs/types");
|
|
@@ -107,7 +435,7 @@ var buildStrategyLiveDiagnosis = (input) => {
|
|
|
107
435
|
};
|
|
108
436
|
|
|
109
437
|
// src/lib/strategyRelease/monitoringProfile.ts
|
|
110
|
-
var
|
|
438
|
+
var DAY_MS2 = 864e5;
|
|
111
439
|
function assert(condition, message) {
|
|
112
440
|
if (!condition) throw new Error(`Invalid strategy release: ${message}`);
|
|
113
441
|
}
|
|
@@ -163,13 +491,13 @@ var buildStrategyMonitoringProfile = ({
|
|
|
163
491
|
Number.isInteger(windowDays) && windowDays > 0,
|
|
164
492
|
"monitoring profile days must be positive integers"
|
|
165
493
|
);
|
|
166
|
-
const duration = windowDays *
|
|
494
|
+
const duration = windowDays * DAY_MS2;
|
|
167
495
|
assert(
|
|
168
496
|
endTime - startTime >= duration,
|
|
169
497
|
`${windowDays}d monitoring window exceeds historical coverage`
|
|
170
498
|
);
|
|
171
499
|
const drawdowns = [];
|
|
172
|
-
for (let windowStart = startTime; windowStart + duration <= endTime; windowStart +=
|
|
500
|
+
for (let windowStart = startTime; windowStart + duration <= endTime; windowStart += DAY_MS2) {
|
|
173
501
|
const windowEnd = windowStart + duration;
|
|
174
502
|
drawdowns.push(
|
|
175
503
|
realizedDrawdown(
|
|
@@ -197,54 +525,25 @@ var buildStrategyMonitoringProfile = ({
|
|
|
197
525
|
};
|
|
198
526
|
};
|
|
199
527
|
|
|
200
|
-
// src/lib/strategyRelease/evidenceRetention.ts
|
|
201
|
-
var DAY_MS2 = 864e5;
|
|
202
|
-
var planStrategyEvidenceRetention = ({
|
|
203
|
-
now,
|
|
204
|
-
entries,
|
|
205
|
-
retentionDays = {
|
|
206
|
-
operational_redis: 3,
|
|
207
|
-
verbose_payload: 14,
|
|
208
|
-
verified_runtime_bundle: 90,
|
|
209
|
-
compact_ledger: null
|
|
210
|
-
}
|
|
211
|
-
}) => {
|
|
212
|
-
const keep = [];
|
|
213
|
-
const remove = [];
|
|
214
|
-
for (const entry of entries) {
|
|
215
|
-
const days = retentionDays[entry.kind];
|
|
216
|
-
if (days == null || !entry.verified || !entry.aggregated || now - entry.createdAt <= days * DAY_MS2) {
|
|
217
|
-
keep.push(entry);
|
|
218
|
-
} else {
|
|
219
|
-
remove.push(entry);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return {
|
|
223
|
-
keep,
|
|
224
|
-
delete: remove,
|
|
225
|
-
bytesReclaimable: remove.reduce((total, entry) => total + entry.bytes, 0)
|
|
226
|
-
};
|
|
227
|
-
};
|
|
228
|
-
|
|
229
528
|
// src/lib/strategyRelease.ts
|
|
230
|
-
var
|
|
529
|
+
var SHA256_RE2 = /^[a-f0-9]{64}$/;
|
|
231
530
|
var LINEAGE_FINGERPRINT_RE = /^[a-f0-9]{16}$/;
|
|
232
531
|
var STRATEGY_REVISION_RE = /^sr1:[a-f0-9]{16}$/;
|
|
233
532
|
var DEPLOYMENT_COMPOSITION_ID_RE = /^dc1:[a-f0-9]{16}$/;
|
|
234
533
|
var REQUIRED_LONG_WINDOWS = [1095, 1460, 1825];
|
|
235
534
|
var REQUIRED_DIAGNOSTIC_TERMINAL_WINDOWS = [365, 180, 90];
|
|
236
535
|
var MIN_RECENT_DIRECTION_REPAIR_EVENTS = 20;
|
|
237
|
-
var safeSegment =
|
|
238
|
-
var compactTimestamp =
|
|
536
|
+
var safeSegment = import_strategyReleaseEvidence2.safeStrategyEvidenceSegment;
|
|
537
|
+
var compactTimestamp = import_strategyReleaseEvidence2.compactStrategyEvidenceTimestamp;
|
|
239
538
|
var verifyFullPeriodChartArtifact = async (input) => {
|
|
240
539
|
const artifact = input.chartArtifact;
|
|
241
|
-
if (!artifact?.path || !
|
|
540
|
+
if (!artifact?.path || !SHA256_RE2.test(artifact.sha256)) return false;
|
|
242
541
|
try {
|
|
243
|
-
const artifactPath =
|
|
244
|
-
if (await (0,
|
|
542
|
+
const artifactPath = import_node_path3.default.resolve(artifact.path);
|
|
543
|
+
if (await (0, import_strategyReleaseEvidence2.strategyEvidenceFileSha256)(artifactPath) !== artifact.sha256) {
|
|
245
544
|
return false;
|
|
246
545
|
}
|
|
247
|
-
const report = JSON.parse(await
|
|
546
|
+
const report = JSON.parse(await import_promises2.default.readFile(artifactPath, "utf8"));
|
|
248
547
|
return Boolean(
|
|
249
548
|
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
|
|
250
549
|
);
|
|
@@ -253,13 +552,13 @@ var verifyFullPeriodChartArtifact = async (input) => {
|
|
|
253
552
|
}
|
|
254
553
|
};
|
|
255
554
|
var readVerifiedJsonArtifact = async (artifact) => {
|
|
256
|
-
if (!artifact?.path || !
|
|
555
|
+
if (!artifact?.path || !SHA256_RE2.test(artifact.sha256)) return null;
|
|
257
556
|
try {
|
|
258
|
-
const artifactPath =
|
|
259
|
-
if (await (0,
|
|
557
|
+
const artifactPath = import_node_path3.default.resolve(artifact.path);
|
|
558
|
+
if (await (0, import_strategyReleaseEvidence2.strategyEvidenceFileSha256)(artifactPath) !== artifact.sha256) {
|
|
260
559
|
return null;
|
|
261
560
|
}
|
|
262
|
-
return JSON.parse(await
|
|
561
|
+
return JSON.parse(await import_promises2.default.readFile(artifactPath, "utf8"));
|
|
263
562
|
} catch {
|
|
264
563
|
return null;
|
|
265
564
|
}
|
|
@@ -272,7 +571,7 @@ var verifyResearchDecisionLineage = async (input) => {
|
|
|
272
571
|
if (!progress || !selected) return false;
|
|
273
572
|
const progressSelection = progress.selectedComposition;
|
|
274
573
|
return Boolean(
|
|
275
|
-
progress.schema === "tradejs-release-progress/v2" && progress.strategy === input.strategy && progress.status === "complete" && progress.verdictAllowed === true && typeof progress.lineageId === "string" && progress.lineageId.length > 0 && typeof progress.objectiveFingerprint === "string" &&
|
|
574
|
+
progress.schema === "tradejs-release-progress/v2" && progress.strategy === input.strategy && progress.status === "complete" && progress.verdictAllowed === true && typeof progress.lineageId === "string" && progress.lineageId.length > 0 && typeof progress.objectiveFingerprint === "string" && SHA256_RE2.test(progress.objectiveFingerprint) && typeof progressSelection?.candidateId === "string" && progressSelection.candidateId.length > 0 && typeof progressSelection.compositionFingerprint === "string" && SHA256_RE2.test(progressSelection.compositionFingerprint) && progressSelection.artifactSha256 === input.selectedCompositionArtifact?.sha256 && selected.schema === "tradejs-release-selected-composition/v2" && selected.strategy === input.strategy && selected.lineageId === progress.lineageId && selected.objectiveFingerprint === progress.objectiveFingerprint && selected.candidateId === progressSelection.candidateId && selected.compositionFingerprint === progressSelection.compositionFingerprint && selected.chartSha256 === input.chartArtifact?.sha256
|
|
276
575
|
);
|
|
277
576
|
};
|
|
278
577
|
var hasExactRuntimeTarget = (target, strategyName) => Boolean(
|
|
@@ -413,12 +712,12 @@ var deriveStrategyReleaseResearchDecision = async (input) => {
|
|
|
413
712
|
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."
|
|
414
713
|
};
|
|
415
714
|
};
|
|
416
|
-
var canonicalStrategyReleaseJson = (value) => (0,
|
|
417
|
-
var strategyReleaseSha256 = (value) => (0,
|
|
715
|
+
var canonicalStrategyReleaseJson = (value) => (0, import_strategyReleaseEvidence2.canonicalStrategyEvidenceJson)(value);
|
|
716
|
+
var strategyReleaseSha256 = (value) => (0, import_strategyReleaseEvidence2.strategyEvidenceSha256)(value);
|
|
418
717
|
function assert2(condition, message) {
|
|
419
718
|
if (!condition) throw new Error(`Invalid strategy release: ${message}`);
|
|
420
719
|
}
|
|
421
|
-
var assertFingerprint = (value, label) => assert2(
|
|
720
|
+
var assertFingerprint = (value, label) => assert2(SHA256_RE2.test(value), `${label} must be a lowercase SHA-256`);
|
|
422
721
|
var assertLineageFingerprint = (value, label) => assert2(
|
|
423
722
|
LINEAGE_FINGERPRINT_RE.test(value),
|
|
424
723
|
`${label} must be a 16-character lowercase lineage fingerprint`
|
|
@@ -808,7 +1107,7 @@ var createStrategyReleaseEnvelope = (manifest) => ({
|
|
|
808
1107
|
manifest
|
|
809
1108
|
});
|
|
810
1109
|
var verifyStrategyReleaseEnvelope = async (valueOrPath) => {
|
|
811
|
-
const value = typeof valueOrPath === "string" ? JSON.parse(await
|
|
1110
|
+
const value = typeof valueOrPath === "string" ? JSON.parse(await import_promises2.default.readFile(valueOrPath, "utf8")) : valueOrPath;
|
|
812
1111
|
const envelope = value;
|
|
813
1112
|
assert2(
|
|
814
1113
|
envelope?.schema === "tradejs-strategy-release-envelope/v1",
|
|
@@ -859,8 +1158,8 @@ var verifyStrategyReleaseEnvelope = async (valueOrPath) => {
|
|
|
859
1158
|
return envelope;
|
|
860
1159
|
};
|
|
861
1160
|
var verifyStrategyEvidenceMarkerEnvelope = async (valueOrPath) => {
|
|
862
|
-
const value = typeof valueOrPath === "string" ? JSON.parse(await
|
|
863
|
-
return (0,
|
|
1161
|
+
const value = typeof valueOrPath === "string" ? JSON.parse(await import_promises2.default.readFile(valueOrPath, "utf8")) : valueOrPath;
|
|
1162
|
+
return (0, import_strategyReleaseEvidence2.verifyStrategyEvidenceMarkerEnvelope)(value);
|
|
864
1163
|
};
|
|
865
1164
|
var releaseMarkers = (manifest) => {
|
|
866
1165
|
const common = {
|
|
@@ -921,21 +1220,21 @@ var releaseMarkers = (manifest) => {
|
|
|
921
1220
|
};
|
|
922
1221
|
var writeJsonAtomic = async (filePath, value) => {
|
|
923
1222
|
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
924
|
-
await
|
|
925
|
-
await
|
|
1223
|
+
await import_promises2.default.mkdir(import_node_path3.default.dirname(filePath), { recursive: true });
|
|
1224
|
+
await import_promises2.default.writeFile(
|
|
926
1225
|
temporaryPath,
|
|
927
1226
|
`${canonicalStrategyReleaseJson(value)}
|
|
928
1227
|
`,
|
|
929
1228
|
"utf8"
|
|
930
1229
|
);
|
|
931
|
-
await
|
|
1230
|
+
await import_promises2.default.rename(temporaryPath, filePath);
|
|
932
1231
|
};
|
|
933
1232
|
var publishStrategyRelease = async ({
|
|
934
1233
|
rootDir,
|
|
935
1234
|
manifest
|
|
936
1235
|
}) => {
|
|
937
1236
|
const envelope = createStrategyReleaseEnvelope(manifest);
|
|
938
|
-
const markerEnvelope = (0,
|
|
1237
|
+
const markerEnvelope = (0, import_strategyReleaseEvidence2.createStrategyEvidenceMarkerEnvelope)({
|
|
939
1238
|
strategy: manifest.strategy,
|
|
940
1239
|
createdAt: manifest.createdAt,
|
|
941
1240
|
markers: releaseMarkers(manifest),
|
|
@@ -945,13 +1244,13 @@ var publishStrategyRelease = async ({
|
|
|
945
1244
|
path: entry.path
|
|
946
1245
|
}))
|
|
947
1246
|
});
|
|
948
|
-
const releasePath =
|
|
1247
|
+
const releasePath = import_node_path3.default.join(
|
|
949
1248
|
rootDir,
|
|
950
1249
|
"releases",
|
|
951
1250
|
safeSegment(manifest.strategy, "strategy"),
|
|
952
1251
|
`${manifest.releaseId}.json`
|
|
953
1252
|
);
|
|
954
|
-
const markerPath =
|
|
1253
|
+
const markerPath = import_node_path3.default.join(
|
|
955
1254
|
rootDir,
|
|
956
1255
|
"markers",
|
|
957
1256
|
safeSegment(manifest.strategy, "strategy"),
|
|
@@ -1025,19 +1324,19 @@ var publishStrategyLiveDiagnosis = async ({
|
|
|
1025
1324
|
severity: diagnosis.verdict === "EXPECTED_DRAWDOWN" ? "info" : "warning"
|
|
1026
1325
|
}
|
|
1027
1326
|
];
|
|
1028
|
-
const markerEnvelope = (0,
|
|
1327
|
+
const markerEnvelope = (0, import_strategyReleaseEvidence2.createStrategyEvidenceMarkerEnvelope)({
|
|
1029
1328
|
strategy: diagnosis.strategy,
|
|
1030
1329
|
createdAt: diagnosis.createdAt,
|
|
1031
1330
|
markers,
|
|
1032
1331
|
sourceArtifacts
|
|
1033
1332
|
});
|
|
1034
|
-
const diagnosisPath =
|
|
1333
|
+
const diagnosisPath = import_node_path3.default.join(
|
|
1035
1334
|
rootDir,
|
|
1036
1335
|
"diagnoses",
|
|
1037
1336
|
safeSegment(diagnosis.strategy, "strategy"),
|
|
1038
1337
|
`${diagnosisId}.json`
|
|
1039
1338
|
);
|
|
1040
|
-
const markerPath =
|
|
1339
|
+
const markerPath = import_node_path3.default.join(
|
|
1041
1340
|
rootDir,
|
|
1042
1341
|
"markers",
|
|
1043
1342
|
safeSegment(diagnosis.strategy, "strategy"),
|
|
@@ -1227,11 +1526,11 @@ var verifyCompletedCoreResearchBundle = async ({
|
|
|
1227
1526
|
const specSha256 = finiteString(artifact.specSha256);
|
|
1228
1527
|
assert2(researchId != null, "core result researchId is required");
|
|
1229
1528
|
assert2(specSha256 != null, "core result specSha256 is required");
|
|
1230
|
-
const researchDir =
|
|
1231
|
-
const manifestPath =
|
|
1232
|
-
const specPath =
|
|
1233
|
-
const manifestBytes = await
|
|
1234
|
-
const specBytes = await
|
|
1529
|
+
const researchDir = import_node_path3.default.dirname(resultPath);
|
|
1530
|
+
const manifestPath = import_node_path3.default.join(researchDir, "manifest.json");
|
|
1531
|
+
const specPath = import_node_path3.default.join(researchDir, "spec.json");
|
|
1532
|
+
const manifestBytes = await import_promises2.default.readFile(manifestPath).catch(() => null);
|
|
1533
|
+
const specBytes = await import_promises2.default.readFile(specPath).catch(() => null);
|
|
1235
1534
|
assert2(
|
|
1236
1535
|
manifestBytes != null && specBytes != null,
|
|
1237
1536
|
"core result must belong to a completed core research bundle"
|
|
@@ -1240,22 +1539,22 @@ var verifyCompletedCoreResearchBundle = async ({
|
|
|
1240
1539
|
const artifactHashes = manifest.artifactHashes != null && typeof manifest.artifactHashes === "object" && !Array.isArray(manifest.artifactHashes) ? manifest.artifactHashes : null;
|
|
1241
1540
|
const parsedSpec = JSON.parse(specBytes.toString("utf8"));
|
|
1242
1541
|
assert2(
|
|
1243
|
-
manifest.schema === "tradejs-core-research-manifest/v1" && manifest.status === "completed" && manifest.researchId === researchId && manifest.specSha256 === specSha256 && artifactHashes?.["result.json"] === resultSha256 && (0,
|
|
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,
|
|
1244
1543
|
"core result is not bound by its completed core research manifest"
|
|
1245
1544
|
);
|
|
1246
1545
|
for (const [relativePath, expectedSha256] of Object.entries(
|
|
1247
1546
|
artifactHashes ?? {}
|
|
1248
1547
|
)) {
|
|
1249
1548
|
assert2(
|
|
1250
|
-
!
|
|
1549
|
+
!import_node_path3.default.isAbsolute(relativePath) && !relativePath.split(/[\\/]/).includes(".."),
|
|
1251
1550
|
`core bundle artifact ${relativePath} escapes its research directory`
|
|
1252
1551
|
);
|
|
1253
1552
|
assert2(
|
|
1254
|
-
typeof expectedSha256 === "string" &&
|
|
1553
|
+
typeof expectedSha256 === "string" && SHA256_RE2.test(expectedSha256),
|
|
1255
1554
|
`core bundle artifact ${relativePath} has an invalid checksum`
|
|
1256
1555
|
);
|
|
1257
|
-
const actualSha256 = await (0,
|
|
1258
|
-
|
|
1556
|
+
const actualSha256 = await (0, import_strategyReleaseEvidence2.strategyEvidenceFileSha256)(
|
|
1557
|
+
import_node_path3.default.join(researchDir, relativePath)
|
|
1259
1558
|
);
|
|
1260
1559
|
assert2(
|
|
1261
1560
|
actualSha256 === expectedSha256,
|
|
@@ -1266,7 +1565,7 @@ var verifyCompletedCoreResearchBundle = async ({
|
|
|
1266
1565
|
async function collectReleaseEvidenceReferences(references) {
|
|
1267
1566
|
return Promise.all(
|
|
1268
1567
|
references.map(async (reference) => {
|
|
1269
|
-
const actualSha256 = await (0,
|
|
1568
|
+
const actualSha256 = await (0, import_strategyReleaseEvidence2.strategyEvidenceFileSha256)(reference.path);
|
|
1270
1569
|
assert2(
|
|
1271
1570
|
actualSha256 === reference.sha256,
|
|
1272
1571
|
`evidence ${reference.artifactId} checksum mismatch`
|
|
@@ -1274,7 +1573,7 @@ async function collectReleaseEvidenceReferences(references) {
|
|
|
1274
1573
|
let artifact;
|
|
1275
1574
|
try {
|
|
1276
1575
|
const parsed = JSON.parse(
|
|
1277
|
-
await
|
|
1576
|
+
await import_promises2.default.readFile(reference.path, "utf8")
|
|
1278
1577
|
);
|
|
1279
1578
|
assert2(
|
|
1280
1579
|
parsed != null && typeof parsed === "object" && !Array.isArray(parsed),
|
|
@@ -1433,17 +1732,17 @@ var required = (value, name) => {
|
|
|
1433
1732
|
};
|
|
1434
1733
|
var writeJsonAtomic2 = async (filePath, value) => {
|
|
1435
1734
|
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
1436
|
-
await
|
|
1437
|
-
await
|
|
1735
|
+
await import_promises3.default.mkdir(import_node_path4.default.dirname(filePath), { recursive: true });
|
|
1736
|
+
await import_promises3.default.writeFile(
|
|
1438
1737
|
temporaryPath,
|
|
1439
1738
|
`${JSON.stringify(value, null, 2)}
|
|
1440
1739
|
`,
|
|
1441
1740
|
"utf8"
|
|
1442
1741
|
);
|
|
1443
|
-
await
|
|
1742
|
+
await import_promises3.default.rename(temporaryPath, filePath);
|
|
1444
1743
|
};
|
|
1445
1744
|
var runStrategyReleaseCommand = async (options) => {
|
|
1446
|
-
const inputPath =
|
|
1745
|
+
const inputPath = import_node_path4.default.resolve(options.inputPath);
|
|
1447
1746
|
if (options.command === "verify") {
|
|
1448
1747
|
const envelope = await verifyStrategyReleaseEnvelope(inputPath);
|
|
1449
1748
|
return {
|
|
@@ -1481,7 +1780,7 @@ var runStrategyReleaseCommand = async (options) => {
|
|
|
1481
1780
|
endTime,
|
|
1482
1781
|
days: options.days?.length ? options.days : [7, 30, 90]
|
|
1483
1782
|
});
|
|
1484
|
-
const outputPath =
|
|
1783
|
+
const outputPath = import_node_path4.default.resolve(
|
|
1485
1784
|
options.outputPath ?? "output/strategy-monitoring-profile.json"
|
|
1486
1785
|
);
|
|
1487
1786
|
await writeJsonAtomic2(outputPath, profile);
|
|
@@ -1492,22 +1791,40 @@ var runStrategyReleaseCommand = async (options) => {
|
|
|
1492
1791
|
profile
|
|
1493
1792
|
};
|
|
1494
1793
|
}
|
|
1495
|
-
const input = JSON.parse(await
|
|
1794
|
+
const input = JSON.parse(await import_promises3.default.readFile(inputPath, "utf8"));
|
|
1496
1795
|
if (options.command === "decide") {
|
|
1497
1796
|
const decision = await deriveStrategyReleaseResearchDecision(input);
|
|
1498
1797
|
if (options.outputPath) {
|
|
1499
|
-
await writeJsonAtomic2(
|
|
1798
|
+
await writeJsonAtomic2(import_node_path4.default.resolve(options.outputPath), decision);
|
|
1500
1799
|
}
|
|
1501
1800
|
return {
|
|
1502
1801
|
kind: "decided",
|
|
1503
1802
|
...decision,
|
|
1504
|
-
outputPath: options.outputPath ?
|
|
1803
|
+
outputPath: options.outputPath ? import_node_path4.default.resolve(options.outputPath) : null
|
|
1505
1804
|
};
|
|
1506
1805
|
}
|
|
1507
1806
|
if (options.command === "retention") {
|
|
1508
1807
|
const plan = planStrategyEvidenceRetention(input);
|
|
1509
|
-
|
|
1510
|
-
|
|
1808
|
+
const researchEntries = plan.delete.filter(
|
|
1809
|
+
(entry) => entry.kind === "research_payload" || entry.kind === "research_cache"
|
|
1810
|
+
);
|
|
1811
|
+
const receiptPath = options.outputPath ?? `${inputPath}.receipt.json`;
|
|
1812
|
+
if (researchEntries.length) {
|
|
1813
|
+
if (researchEntries.length !== plan.delete.length) {
|
|
1814
|
+
throw new Error(
|
|
1815
|
+
"Research cleanup requires a separate inventory from runtime retention"
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
await applyResearchRetention({
|
|
1819
|
+
projectRoot: process.env.PROJECT_CWD ?? process.cwd(),
|
|
1820
|
+
research: input.research,
|
|
1821
|
+
entries: researchEntries,
|
|
1822
|
+
receiptPath,
|
|
1823
|
+
apply: Boolean(options.apply),
|
|
1824
|
+
protectedPaths: [inputPath]
|
|
1825
|
+
});
|
|
1826
|
+
} else if (options.apply) {
|
|
1827
|
+
await Promise.all(plan.delete.map((entry) => import_promises3.default.rm(entry.path)));
|
|
1511
1828
|
}
|
|
1512
1829
|
return {
|
|
1513
1830
|
kind: "retention",
|
|
@@ -1515,17 +1832,18 @@ var runStrategyReleaseCommand = async (options) => {
|
|
|
1515
1832
|
keepCount: plan.keep.length,
|
|
1516
1833
|
deleteCount: plan.delete.length,
|
|
1517
1834
|
bytesReclaimable: plan.bytesReclaimable,
|
|
1518
|
-
delete: plan.delete.map((entry) => entry.path)
|
|
1835
|
+
delete: plan.delete.map((entry) => entry.path),
|
|
1836
|
+
...researchEntries.length && options.apply ? { receiptPath } : {}
|
|
1519
1837
|
};
|
|
1520
1838
|
}
|
|
1521
1839
|
if (options.command === "diagnose") {
|
|
1522
1840
|
const diagnosis = buildStrategyLiveDiagnosis(input);
|
|
1523
|
-
const outputPath =
|
|
1841
|
+
const outputPath = import_node_path4.default.resolve(
|
|
1524
1842
|
options.outputPath ?? "output/strategy-live-diagnosis.json"
|
|
1525
1843
|
);
|
|
1526
1844
|
await writeJsonAtomic2(outputPath, diagnosis);
|
|
1527
1845
|
const published2 = options.rootDir ? await publishStrategyLiveDiagnosis({
|
|
1528
|
-
rootDir:
|
|
1846
|
+
rootDir: import_node_path4.default.resolve(options.rootDir),
|
|
1529
1847
|
diagnosis,
|
|
1530
1848
|
sourceArtifacts: input.sourceArtifacts ?? []
|
|
1531
1849
|
}) : null;
|
|
@@ -1542,7 +1860,7 @@ var runStrategyReleaseCommand = async (options) => {
|
|
|
1542
1860
|
evidence: await collectReleaseEvidenceReferences(input.evidence ?? [])
|
|
1543
1861
|
});
|
|
1544
1862
|
const published = await publishStrategyRelease({
|
|
1545
|
-
rootDir:
|
|
1863
|
+
rootDir: import_node_path4.default.resolve(options.rootDir ?? "data/strategy-release"),
|
|
1546
1864
|
manifest
|
|
1547
1865
|
});
|
|
1548
1866
|
return {
|