@tuned-tensor/cli 0.21.0 → 0.22.0

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.
@@ -1010,11 +1010,34 @@ var foundationHyperparametersSchema = z.object({
1010
1010
  keep_checkpoints: z.number().int().min(1).max(100).optional(),
1011
1011
  log_interval_steps: z.number().int().min(1).max(1e6).optional()
1012
1012
  }).strict();
1013
+ var huggingFaceRepoIdSchema = z.string().regex(
1014
+ /^[A-Za-z0-9][A-Za-z0-9._-]{0,95}\/[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/,
1015
+ "huggingface.repo must be a Hugging Face dataset id such as org/name"
1016
+ );
1017
+ var datasetColumnsSchema = z.object({
1018
+ input: z.string().min(1),
1019
+ output: z.string().min(1)
1020
+ }).strict();
1021
+ var huggingFaceDatasetSourceSchema = z.object({
1022
+ repo: huggingFaceRepoIdSchema,
1023
+ revision: baseModelRevisionSchema.optional(),
1024
+ /**
1025
+ * Field names for plain JSONL records such as `{"text": ..., "label": ...}`.
1026
+ * Each record becomes a chat row with the spec's compiled system message.
1027
+ * Omit for files that are already TT chat JSONL.
1028
+ */
1029
+ columns: datasetColumnsSchema.optional()
1030
+ }).strict();
1031
+ function isRepoRelativePath(value) {
1032
+ if (/^(?:[A-Za-z]:|[/\\]|file:)/.test(value)) return false;
1033
+ return value.split(/[/\\]/).every((part) => part !== "" && part !== "." && part !== "..");
1034
+ }
1013
1035
  var datasetPrebuiltSchema = z.object({
1014
1036
  training: z.string().min(1),
1015
1037
  validation: z.string().min(1).optional(),
1016
1038
  test: z.string().min(1).optional(),
1017
- format: datasetFormatSchema.default("chat_jsonl")
1039
+ format: datasetFormatSchema.default("chat_jsonl"),
1040
+ huggingface: huggingFaceDatasetSourceSchema.optional()
1018
1041
  }).strict().superRefine((dataset, context) => {
1019
1042
  if (!dataset.validation && !dataset.test) {
1020
1043
  context.addIssue({
@@ -1023,7 +1046,57 @@ var datasetPrebuiltSchema = z.object({
1023
1046
  message: "Provide a distinct validation or test split for held-out evaluation"
1024
1047
  });
1025
1048
  }
1049
+ if (!dataset.huggingface) return;
1050
+ for (const key of ["training", "validation", "test"]) {
1051
+ const value = dataset[key];
1052
+ if (value === void 0) continue;
1053
+ if (!isRepoRelativePath(value)) {
1054
+ context.addIssue({
1055
+ code: z.ZodIssueCode.custom,
1056
+ path: [key],
1057
+ message: `With dataset_prebuilt.huggingface, ${key} must be a file path inside the dataset repo, such as data/${key}.jsonl`
1058
+ });
1059
+ } else if (!value.toLowerCase().endsWith(".jsonl")) {
1060
+ context.addIssue({
1061
+ code: z.ZodIssueCode.custom,
1062
+ path: [key],
1063
+ message: `${key} must be a chat JSONL (.jsonl) file; Parquet, CSV and other formats are not supported yet`
1064
+ });
1065
+ }
1066
+ }
1026
1067
  });
1068
+ var datasetSourceFileSchema = z.object({
1069
+ path: z.string().min(1),
1070
+ size_bytes: z.number().int().nonnegative(),
1071
+ sha256: z.string().regex(/^[a-f0-9]{64}$/)
1072
+ }).strict();
1073
+ var datasetConversionStatsSchema = z.object({
1074
+ records: z.number().int().nonnegative(),
1075
+ kept: z.number().int().nonnegative(),
1076
+ skipped_missing_fields: z.number().int().nonnegative(),
1077
+ dropped_duplicate_inputs: z.number().int().nonnegative(),
1078
+ dropped_eval_overlap: z.number().int().nonnegative()
1079
+ }).strict();
1080
+ var datasetSourceSchema = z.object({
1081
+ provider: z.literal("huggingface"),
1082
+ repo: huggingFaceRepoIdSchema,
1083
+ requested_revision: baseModelRevisionSchema.nullable(),
1084
+ revision: baseModelRevisionSchema,
1085
+ pinned: z.boolean(),
1086
+ columns: datasetColumnsSchema.nullable(),
1087
+ /** Per-split record accounting when `columns` converted plain JSONL records. */
1088
+ conversion: z.object({
1089
+ training: datasetConversionStatsSchema,
1090
+ validation: datasetConversionStatsSchema.optional(),
1091
+ test: datasetConversionStatsSchema.optional()
1092
+ }).strict().nullable().optional(),
1093
+ snapshot_path: z.string().min(1),
1094
+ files: z.object({
1095
+ training: datasetSourceFileSchema,
1096
+ validation: datasetSourceFileSchema.optional(),
1097
+ test: datasetSourceFileSchema.optional()
1098
+ }).strict()
1099
+ }).strict();
1027
1100
  var fineTuneHyperparametersSchema = z.object({
1028
1101
  n_epochs: z.number().int().min(1).max(20).default(1),
1029
1102
  learning_rate: z.number().positive().optional(),
@@ -1053,7 +1126,8 @@ var fineTuneRunRequestSchema = z.object({
1053
1126
  run_number: z.number().int().min(1),
1054
1127
  spec_snapshot: specSnapshotSchema,
1055
1128
  hyperparameters: fineTuneHyperparametersSchema.default({ n_epochs: 1 }),
1056
- dataset_prebuilt: datasetPrebuiltSchema.optional()
1129
+ dataset_prebuilt: datasetPrebuiltSchema.optional(),
1130
+ dataset_source: datasetSourceSchema.optional()
1057
1131
  }).strict().superRefine((request, context) => {
1058
1132
  requirePinnedUncertifiedModel(request, context);
1059
1133
  if (request.spec_snapshot.examples.length === 0 && !request.dataset_prebuilt) {
@@ -1143,6 +1217,55 @@ var trainingReportSchema = z.object({
1143
1217
  log_uri: z.string(),
1144
1218
  command: z.array(z.string()).optional()
1145
1219
  }).strict();
1220
+ var runProvenanceSchema = z.object({
1221
+ schema_version: z.literal(1),
1222
+ recorded_at: z.string(),
1223
+ software: z.object({
1224
+ tt_version: z.string(),
1225
+ node_version: z.string(),
1226
+ platform: z.string(),
1227
+ arch: z.string(),
1228
+ runtime_fingerprint: z.string(),
1229
+ python_lock_sha256: z.string().nullable()
1230
+ }).strict(),
1231
+ execution: z.object({
1232
+ target: z.enum(["local", "aws"]),
1233
+ training_backend: z.string(),
1234
+ instance_id: z.string().optional(),
1235
+ region: z.string().optional()
1236
+ }).strict(),
1237
+ spec: z.object({
1238
+ behavior_spec_id: z.string(),
1239
+ run_number: z.number().int(),
1240
+ snapshot_sha256: z.string(),
1241
+ system_prompt_sha256: z.string(),
1242
+ file_path: z.string().optional(),
1243
+ file_sha256: z.string().optional()
1244
+ }).strict(),
1245
+ base_model: z.object({
1246
+ id: z.string(),
1247
+ revision: z.string().nullable(),
1248
+ fingerprint: z.string().nullable()
1249
+ }).strict(),
1250
+ data: z.object({
1251
+ prebuilt: z.boolean(),
1252
+ format: z.string().nullable(),
1253
+ file_sha256: z.record(z.string(), z.string()),
1254
+ compiled_training_sha256: z.string().nullable(),
1255
+ training_examples: z.number().int().nonnegative().nullable(),
1256
+ eval_split: z.string(),
1257
+ eval_sample_seed: z.number().int(),
1258
+ eval_examples_total: z.number().int().nonnegative(),
1259
+ eval_examples_used: z.number().int().nonnegative()
1260
+ }).strict(),
1261
+ training: z.object({
1262
+ hyperparameters: z.record(z.string(), z.unknown()),
1263
+ exit_code: z.number().int().nullable(),
1264
+ log_uri: z.string()
1265
+ }).strict(),
1266
+ /** Hash over request, runtime, preparation config and input digests. */
1267
+ source_fingerprint: z.string()
1268
+ }).strict();
1146
1269
  var runReportSchema = z.object({
1147
1270
  run_id: z.string().uuid(),
1148
1271
  behavior_spec_id: z.string().uuid(),
@@ -1168,6 +1291,7 @@ var runReportSchema = z.object({
1168
1291
  failures: z.array(z.string())
1169
1292
  }).strict().optional(),
1170
1293
  training: trainingReportSchema,
1294
+ provenance: runProvenanceSchema.optional(),
1171
1295
  artifact_uris: z.object({
1172
1296
  dataset: z.string(),
1173
1297
  baseline_eval: z.string(),
@@ -1182,6 +1306,7 @@ var runReportSchema = z.object({
1182
1306
  dataset_prebuilt: z.boolean(),
1183
1307
  dataset_format: datasetFormatSchema.nullable().optional(),
1184
1308
  dataset_uri: z.string(),
1309
+ dataset_source: datasetSourceSchema.optional(),
1185
1310
  spec_example_count: z.number().int().nonnegative(),
1186
1311
  training_example_count: z.number().int().nonnegative().nullable(),
1187
1312
  eval_examples_total: z.number().int().nonnegative(),
@@ -1389,6 +1514,9 @@ async function loadNormalizedChatJsonl(path) {
1389
1514
  async function normalizeChatJsonlForRelocation(path) {
1390
1515
  return (await loadNormalizedChatJsonl(path)).jsonl;
1391
1516
  }
1517
+ function datasetInputIdentity(value) {
1518
+ return value.normalize("NFKC").trim().replace(/\s+/g, " ").toLocaleLowerCase("en");
1519
+ }
1392
1520
  function buildSystemMessage(spec) {
1393
1521
  const parts = [];
1394
1522
  const guidelines = spec.guidelines.map((value) => value.trim()).filter(Boolean);
@@ -1606,7 +1734,7 @@ function resolveLocalRunInputPaths(raw, inputPath) {
1606
1734
  const dataset = value.dataset_prebuilt;
1607
1735
  if (dataset && typeof dataset === "object" && !Array.isArray(dataset)) {
1608
1736
  const fields = dataset;
1609
- for (const key of ["training", "validation", "test"]) {
1737
+ if (fields.huggingface === void 0) for (const key of ["training", "validation", "test"]) {
1610
1738
  if (fields[key] !== void 0) fields[key] = resolveLocalReference(fields[key], baseDirectory);
1611
1739
  }
1612
1740
  }
@@ -2186,10 +2314,301 @@ function localRuntimePackageRoot(moduleUrl) {
2186
2314
  return root;
2187
2315
  }
2188
2316
 
2317
+ // src/local-runtime/audit.ts
2318
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
2319
+ import { createReadStream as createReadStream2 } from "fs";
2320
+ import { mkdir as mkdir3, open as open2, readFile as readFile4, rename as rename2, rm, stat as stat3, writeFile as writeFile3 } from "fs/promises";
2321
+ import { hostname } from "os";
2322
+ import { join as join3 } from "path";
2323
+ function canonicalJson3(value) {
2324
+ const normalized = JSON.parse(JSON.stringify(value ?? null));
2325
+ const encode = (entry) => {
2326
+ if (entry === null || typeof entry !== "object") return JSON.stringify(entry);
2327
+ if (Array.isArray(entry)) return `[${entry.map(encode).join(",")}]`;
2328
+ const object = entry;
2329
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${encode(object[key])}`).join(",")}}`;
2330
+ };
2331
+ return encode(normalized);
2332
+ }
2333
+ function eventHash(event) {
2334
+ const { hash: _hash, ...body } = event;
2335
+ return createHash3("sha256").update(canonicalJson3(body)).digest("hex");
2336
+ }
2337
+ function chainEvent(event, previous) {
2338
+ const body = JSON.parse(JSON.stringify({
2339
+ ...event,
2340
+ seq: typeof previous?.seq === "number" ? previous.seq + 1 : 0,
2341
+ prev_hash: previous?.hash ?? null
2342
+ }));
2343
+ return { ...body, hash: eventHash(body) };
2344
+ }
2345
+ function verifyEventChain(events) {
2346
+ let legacy = 0;
2347
+ let unchainedAfter = 0;
2348
+ let previous;
2349
+ const result = (status, brk) => ({
2350
+ status,
2351
+ events: events.length,
2352
+ chained_events: events.filter((event) => event.hash !== void 0).length,
2353
+ legacy_events: legacy,
2354
+ unchained_after_chain: unchainedAfter,
2355
+ head_hash: previous?.hash ?? null,
2356
+ ...brk ? { break: brk } : {}
2357
+ });
2358
+ for (const [index, event] of events.entries()) {
2359
+ const chained = event;
2360
+ if (chained.hash === void 0) {
2361
+ if (previous) unchainedAfter += 1;
2362
+ else legacy += 1;
2363
+ continue;
2364
+ }
2365
+ const expectedSeq = previous ? (previous.seq ?? -1) + 1 : 0;
2366
+ if (chained.seq !== expectedSeq) {
2367
+ return result("broken", { index, seq: chained.seq ?? null, reason: `Expected sequence ${expectedSeq}; an event was removed, reordered or inserted.` });
2368
+ }
2369
+ if ((chained.prev_hash ?? null) !== (previous?.hash ?? null)) {
2370
+ return result("broken", { index, seq: chained.seq, reason: "The previous-event hash does not match; an earlier event was changed or removed." });
2371
+ }
2372
+ if (eventHash(chained) !== chained.hash) {
2373
+ return result("broken", { index, seq: chained.seq, reason: "The event's contents do not match its hash; it was edited." });
2374
+ }
2375
+ previous = chained;
2376
+ }
2377
+ if (events.length === 0) return result("empty");
2378
+ if (!previous) return result("legacy");
2379
+ if (unchainedAfter > 0) return result("mixed");
2380
+ return result(legacy > 0 ? "legacy" : "verified");
2381
+ }
2382
+ async function sha256File2(path) {
2383
+ const hash = createHash3("sha256");
2384
+ for await (const chunk of createReadStream2(path)) hash.update(chunk);
2385
+ return hash.digest("hex");
2386
+ }
2387
+ function processAlive(pid) {
2388
+ try {
2389
+ process.kill(pid, 0);
2390
+ return true;
2391
+ } catch (error) {
2392
+ return error.code === "EPERM";
2393
+ }
2394
+ }
2395
+ async function readLockOwner(lockPath) {
2396
+ try {
2397
+ return JSON.parse(await readFile4(join3(lockPath, "owner.json"), "utf8"));
2398
+ } catch {
2399
+ return null;
2400
+ }
2401
+ }
2402
+ async function abandonedLockOwner(lockPath, orphanMs, remoteMs) {
2403
+ const metadata = await stat3(lockPath).catch(() => null);
2404
+ if (!metadata) return null;
2405
+ const owner = await readLockOwner(lockPath);
2406
+ const age = Date.now() - metadata.mtimeMs;
2407
+ if (!owner) return age > orphanMs ? "ownerless" : null;
2408
+ if (owner.host === hostname()) return processAlive(owner.pid) ? null : owner;
2409
+ return age > remoteMs ? owner : null;
2410
+ }
2411
+ async function breakAbandonedLock(lockPath, abandoned) {
2412
+ const moved = `${lockPath}.abandoned-${randomUUID3()}`;
2413
+ try {
2414
+ await rename2(lockPath, moved);
2415
+ } catch (error) {
2416
+ if (error.code === "ENOENT") return;
2417
+ throw error;
2418
+ }
2419
+ const owner = await readLockOwner(moved);
2420
+ const expected = abandoned === "ownerless" ? void 0 : abandoned.token;
2421
+ if (owner?.token !== expected) {
2422
+ await rename2(moved, lockPath).catch(() => void 0);
2423
+ }
2424
+ await rm(moved, { recursive: true, force: true });
2425
+ }
2426
+ async function withAppendLock(lockPath, action, options = {}) {
2427
+ const timeoutMs = options.timeoutMs ?? 6e4;
2428
+ const token = randomUUID3();
2429
+ const deadline = Date.now() + timeoutMs;
2430
+ for (; ; ) {
2431
+ try {
2432
+ await mkdir3(lockPath, { mode: 448 });
2433
+ await writeFile3(join3(lockPath, "owner.json"), JSON.stringify({ token, pid: process.pid, host: hostname() }), { mode: 384 });
2434
+ break;
2435
+ } catch (error) {
2436
+ if (error.code !== "EEXIST") throw error;
2437
+ const abandoned = await abandonedLockOwner(lockPath, options.orphanMs ?? 5e3, options.remoteMs ?? 6e5);
2438
+ if (abandoned) {
2439
+ await breakAbandonedLock(lockPath, abandoned);
2440
+ continue;
2441
+ }
2442
+ if (Date.now() > deadline) throw new Error(`Timed out waiting for the run event log lock: ${lockPath}`);
2443
+ await new Promise((resolve17) => setTimeout(resolve17, 20));
2444
+ }
2445
+ }
2446
+ try {
2447
+ return await action();
2448
+ } finally {
2449
+ if ((await readLockOwner(lockPath))?.token === token) await rm(lockPath, { recursive: true, force: true });
2450
+ }
2451
+ }
2452
+ async function readJsonlTail(path, accept = () => true) {
2453
+ let handle;
2454
+ try {
2455
+ handle = await open2(path, "r");
2456
+ } catch (error) {
2457
+ if (error.code === "ENOENT") return { last: void 0, partial: false };
2458
+ throw error;
2459
+ }
2460
+ try {
2461
+ const size = (await handle.stat()).size;
2462
+ if (size === 0) return { last: void 0, partial: false };
2463
+ let window = Math.min(size, 64 * 1024);
2464
+ for (; ; ) {
2465
+ const buffer = Buffer.alloc(window);
2466
+ await handle.read(buffer, 0, window, size - window);
2467
+ const text = buffer.toString("utf8");
2468
+ const partial = !text.endsWith("\n");
2469
+ const lines = text.split("\n");
2470
+ const complete = window === size ? lines : lines.slice(1);
2471
+ for (const line of complete.reverse()) {
2472
+ if (!line.trim()) continue;
2473
+ let row;
2474
+ try {
2475
+ row = JSON.parse(line);
2476
+ } catch {
2477
+ continue;
2478
+ }
2479
+ if (row === null || typeof row !== "object" || Array.isArray(row)) continue;
2480
+ if (accept(row)) return { last: row, partial };
2481
+ }
2482
+ if (window === size) return { last: void 0, partial };
2483
+ window = Math.min(size, window * 4);
2484
+ }
2485
+ } finally {
2486
+ await handle.close();
2487
+ }
2488
+ }
2489
+ function parseJsonlTolerant(text) {
2490
+ const rows = [];
2491
+ let unreadable = 0;
2492
+ for (const line of text.split(/\r?\n/)) {
2493
+ if (!line.trim()) continue;
2494
+ try {
2495
+ const row = JSON.parse(line);
2496
+ if (row === null || typeof row !== "object" || Array.isArray(row)) {
2497
+ unreadable += 1;
2498
+ continue;
2499
+ }
2500
+ rows.push(row);
2501
+ } catch {
2502
+ unreadable += 1;
2503
+ }
2504
+ }
2505
+ return { rows, unreadable };
2506
+ }
2507
+ function buildTimeline(events) {
2508
+ const timeline = [];
2509
+ for (const event of events) {
2510
+ const last2 = timeline.at(-1);
2511
+ if (last2 && last2.stage === event.stage) {
2512
+ last2.status = event.status;
2513
+ last2.message = event.message;
2514
+ continue;
2515
+ }
2516
+ if (last2) {
2517
+ last2.ended_at = event.occurred_at;
2518
+ last2.duration_seconds = Math.max(0, (Date.parse(event.occurred_at) - Date.parse(last2.started_at)) / 1e3);
2519
+ }
2520
+ timeline.push({
2521
+ stage: event.stage,
2522
+ status: event.status,
2523
+ message: event.message,
2524
+ started_at: event.occurred_at,
2525
+ ended_at: null,
2526
+ duration_seconds: null
2527
+ });
2528
+ }
2529
+ const last = timeline.at(-1);
2530
+ if (last && ["completed", "failed", "cancelled", "stage_completed"].includes(last.stage)) {
2531
+ last.ended_at = last.started_at;
2532
+ last.duration_seconds = 0;
2533
+ }
2534
+ return timeline;
2535
+ }
2536
+ async function auditRun(store, runId) {
2537
+ const state = await store.getRun(runId);
2538
+ const log = await store.getRunEventLog(state.id);
2539
+ const events = log.events;
2540
+ const chain = verifyEventChain(events);
2541
+ const completion = [...events].reverse().find((event) => event.stage === "completed");
2542
+ const recorded = typeof completion?.details?.report_sha256 === "string" ? completion.details.report_sha256 : null;
2543
+ const recordedManifest = typeof completion?.details?.artifact_manifest_sha256 === "string" ? completion.details.artifact_manifest_sha256 : null;
2544
+ const files = await Promise.all((await store.getRunReportPaths(state.id)).map(async (path) => {
2545
+ const sha256 = await sha256File2(path);
2546
+ return { path, sha256, matches_recorded: recorded ? sha256 === recorded : null };
2547
+ }));
2548
+ const manifestPath = state.artifact_dir ? join3(state.artifact_dir, "artifact-manifest.json") : null;
2549
+ const manifestFile = manifestPath && (await stat3(manifestPath).catch(() => null))?.isFile() ? await sha256File2(manifestPath).then((sha256) => ({
2550
+ path: manifestPath,
2551
+ sha256,
2552
+ matches_recorded: recordedManifest ? sha256 === recordedManifest : null
2553
+ })) : null;
2554
+ const report = await store.getRunReport(state.id).catch(() => null);
2555
+ const findings = [];
2556
+ let verdict = "verified";
2557
+ const tampered = (finding) => {
2558
+ findings.push(finding);
2559
+ verdict = "tampered";
2560
+ };
2561
+ const incomplete = (finding) => {
2562
+ findings.push(finding);
2563
+ if (verdict === "verified") verdict = "incomplete";
2564
+ };
2565
+ const completed = state.status === "completed";
2566
+ if (chain.status === "broken") {
2567
+ tampered(`Event log chain is broken at event ${chain.break.index}: ${chain.break.reason}`);
2568
+ } else if (chain.status === "empty") {
2569
+ if (completed) tampered("The run is completed but its event log is missing or empty.");
2570
+ else incomplete("The run has no events.");
2571
+ } else if (chain.status === "legacy") {
2572
+ incomplete(`${chain.legacy_events} event(s) were recorded before event chaining (older TT version) and cannot be verified.`);
2573
+ } else if (chain.status === "mixed") {
2574
+ incomplete(`${chain.unchained_after_chain} event(s) were written without hashes after chaining began, e.g. by an older TT process.`);
2575
+ }
2576
+ if (log.unreadable_lines > 0) incomplete(`${log.unreadable_lines} event line(s) are unreadable, likely from an interrupted write.`);
2577
+ for (const file of files) {
2578
+ if (file.matches_recorded === false) {
2579
+ tampered(`Report ${file.path} changed after the run completed (sha256 ${file.sha256.slice(0, 12)}, recorded ${recorded.slice(0, 12)}).`);
2580
+ }
2581
+ }
2582
+ if (manifestFile?.matches_recorded === false) {
2583
+ tampered(`Artifact manifest ${manifestFile.path} changed after the run completed.`);
2584
+ }
2585
+ if (!completed) {
2586
+ incomplete(`The run is ${state.status}; only completed runs have recorded report digests.`);
2587
+ } else if (!recorded) {
2588
+ if (chain.status === "verified") tampered("The completion event is missing from a chained event log; the log was truncated.");
2589
+ else if (chain.status !== "broken" && chain.status !== "empty") incomplete("No report digest was recorded at completion (older TT version).");
2590
+ } else if (files.length === 0) {
2591
+ incomplete("The recorded report file is missing.");
2592
+ }
2593
+ if (completed && recordedManifest && !manifestFile) incomplete("The artifact manifest recorded at completion is missing.");
2594
+ if (completed && report && !report.provenance) incomplete("The report has no provenance block (older TT version).");
2595
+ return {
2596
+ run_id: state.id,
2597
+ status: state.status,
2598
+ verdict,
2599
+ findings,
2600
+ chain: { ...chain, unreadable_lines: log.unreadable_lines },
2601
+ report: { recorded_sha256: recorded, files },
2602
+ artifact_manifest: { recorded_sha256: recordedManifest, file: manifestFile },
2603
+ timeline: buildTimeline(events),
2604
+ provenance: report?.provenance ?? null
2605
+ };
2606
+ }
2607
+
2189
2608
  // src/local-runtime/store.ts
2190
- import { appendFile, chmod, copyFile as copyFile2, lstat as lstat3, mkdir as mkdir3, readdir as readdir3, readFile as readFile4, rename as rename2, rm, stat as stat3, writeFile as writeFile3 } from "fs/promises";
2191
- import { dirname as dirname4, join as join3, resolve as resolve6, sep as sep3 } from "path";
2192
- import { randomUUID as randomUUID3 } from "crypto";
2609
+ import { appendFile, chmod, copyFile as copyFile2, lstat as lstat3, mkdir as mkdir4, readdir as readdir3, readFile as readFile5, rename as rename3, rm as rm2, stat as stat4, writeFile as writeFile4 } from "fs/promises";
2610
+ import { dirname as dirname4, join as join4, resolve as resolve6, sep as sep3 } from "path";
2611
+ import { randomUUID as randomUUID4 } from "crypto";
2193
2612
  function isTerminalRunState(state) {
2194
2613
  return state.status === "stage_completed" || state.status === "completed" || state.status === "failed" || state.status === "cancelled" && state.current_stage === "cancelled" && Boolean(state.completed_at);
2195
2614
  }
@@ -2199,22 +2618,22 @@ function defaultLocalHome() {
2199
2618
  function localStorePaths(root) {
2200
2619
  return {
2201
2620
  root,
2202
- specsDir: join3(root, "specs"),
2203
- runsDir: join3(root, "runs"),
2204
- modelsDir: join3(root, "models"),
2205
- datasetsDir: join3(root, "datasets")
2621
+ specsDir: join4(root, "specs"),
2622
+ runsDir: join4(root, "runs"),
2623
+ modelsDir: join4(root, "models"),
2624
+ datasetsDir: join4(root, "datasets")
2206
2625
  };
2207
2626
  }
2208
2627
  async function exists2(path) {
2209
2628
  try {
2210
- await stat3(path);
2629
+ await stat4(path);
2211
2630
  return true;
2212
2631
  } catch {
2213
2632
  return false;
2214
2633
  }
2215
2634
  }
2216
2635
  async function ensurePrivateDirectory(path) {
2217
- await mkdir3(path, { recursive: true, mode: 448 });
2636
+ await mkdir4(path, { recursive: true, mode: 448 });
2218
2637
  const metadata = await lstat3(path);
2219
2638
  if (metadata.isSymbolicLink()) {
2220
2639
  throw new Error(`Refusing symbolic link in local store: ${path}`);
@@ -2232,7 +2651,7 @@ async function hardenPrivateTree(path) {
2232
2651
  if (metadata.isDirectory()) {
2233
2652
  await chmod(path, 448);
2234
2653
  for (const entry of await readdir3(path)) {
2235
- await hardenPrivateTree(join3(path, entry));
2654
+ await hardenPrivateTree(join4(path, entry));
2236
2655
  }
2237
2656
  return;
2238
2657
  }
@@ -2262,17 +2681,17 @@ async function hardenExistingLocalStore(root) {
2262
2681
  }
2263
2682
  async function writePrivateJsonAtomic(path, value) {
2264
2683
  await ensurePrivateDirectory(dirname4(path));
2265
- const tmp = `${path}.${process.pid}.${Date.now()}.${randomUUID3()}.tmp`;
2266
- await writeFile3(tmp, `${JSON.stringify(value, null, 2)}
2684
+ const tmp = `${path}.${process.pid}.${Date.now()}.${randomUUID4()}.tmp`;
2685
+ await writeFile4(tmp, `${JSON.stringify(value, null, 2)}
2267
2686
  `, {
2268
2687
  encoding: "utf8",
2269
2688
  mode: 384
2270
2689
  });
2271
- await rename2(tmp, path);
2690
+ await rename3(tmp, path);
2272
2691
  await chmod(path, 384);
2273
2692
  }
2274
2693
  async function readJson2(path) {
2275
- return JSON.parse(await readFile4(path, "utf8"));
2694
+ return JSON.parse(await readFile5(path, "utf8"));
2276
2695
  }
2277
2696
  async function appendJsonl(path, value) {
2278
2697
  await ensurePrivateDirectory(dirname4(path));
@@ -2285,13 +2704,9 @@ async function appendJsonl(path, value) {
2285
2704
  }
2286
2705
  async function writePrivateText(path, value) {
2287
2706
  await ensurePrivateDirectory(dirname4(path));
2288
- await writeFile3(path, value, { encoding: "utf8", mode: 384 });
2707
+ await writeFile4(path, value, { encoding: "utf8", mode: 384 });
2289
2708
  await chmod(path, 384);
2290
2709
  }
2291
- async function readJsonl(path) {
2292
- if (!await exists2(path)) return [];
2293
- return (await readFile4(path, "utf8")).split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
2294
- }
2295
2710
  async function copyIfExists(from, to) {
2296
2711
  if (!await exists2(from)) return;
2297
2712
  await ensurePrivateDirectory(dirname4(to));
@@ -2330,15 +2745,15 @@ function isPathInside(root, candidate) {
2330
2745
  function createLocalStore(root = defaultLocalHome()) {
2331
2746
  const resolvedRoot = resolve6(root);
2332
2747
  const paths = localStorePaths(resolvedRoot);
2333
- const runDir = (id) => join3(paths.runsDir, id);
2334
- const runStatePath = (id) => join3(runDir(id), "state.json");
2335
- const runEventsPath = (id) => join3(runDir(id), "progress.jsonl");
2336
- const runRequestPath = (id) => join3(runDir(id), "request.json");
2337
- const runReportPath = (id) => join3(runDir(id), "run-report.json");
2338
- const cancellationPath = (id) => join3(runDir(id), "cancel.requested");
2339
- const specDir = (id) => join3(paths.specsDir, id);
2340
- const specPath = (id) => join3(specDir(id), "spec.json");
2341
- const modelPath = (id) => join3(paths.modelsDir, id, "model.json");
2748
+ const runDir = (id) => join4(paths.runsDir, id);
2749
+ const runStatePath = (id) => join4(runDir(id), "state.json");
2750
+ const runEventsPath = (id) => join4(runDir(id), "progress.jsonl");
2751
+ const runRequestPath = (id) => join4(runDir(id), "request.json");
2752
+ const runReportPath = (id) => join4(runDir(id), "run-report.json");
2753
+ const cancellationPath = (id) => join4(runDir(id), "cancel.requested");
2754
+ const specDir = (id) => join4(paths.specsDir, id);
2755
+ const specPath = (id) => join4(specDir(id), "spec.json");
2756
+ const modelPath = (id) => join4(paths.modelsDir, id, "model.json");
2342
2757
  let ensurePromise;
2343
2758
  async function ensureOnce() {
2344
2759
  await ensurePrivateDirectory(paths.root);
@@ -2366,8 +2781,8 @@ function createLocalStore(root = defaultLocalHome()) {
2366
2781
  if (!await exists2(path)) return null;
2367
2782
  const [spec, fileStats, directoryStats] = await Promise.all([
2368
2783
  readJson2(path),
2369
- stat3(path),
2370
- stat3(specDir(id))
2784
+ stat4(path),
2785
+ stat4(specDir(id))
2371
2786
  ]);
2372
2787
  const createdAt = directoryStats.birthtimeMs > 0 ? directoryStats.birthtime : directoryStats.ctime;
2373
2788
  return {
@@ -2414,15 +2829,30 @@ function createLocalStore(root = defaultLocalHome()) {
2414
2829
  if (!record) throw new Error(`Failed to persist spec: ${specId}`);
2415
2830
  return record;
2416
2831
  }
2832
+ async function readRunEventLog(id) {
2833
+ const state = await getRun(id);
2834
+ const path = runEventsPath(state.id);
2835
+ if (!await exists2(path)) return { events: [], unreadable_lines: 0 };
2836
+ const parsed = parseJsonlTolerant(await readFile5(path, "utf8"));
2837
+ return { events: parsed.rows, unreadable_lines: parsed.unreadable };
2838
+ }
2417
2839
  async function appendRunEvent(state, event) {
2418
- const row = {
2419
- id: randomUUID3(),
2420
- run_id: state.id,
2421
- occurred_at: (/* @__PURE__ */ new Date()).toISOString(),
2422
- ...event
2423
- };
2424
- await appendJsonl(runEventsPath(state.id), row);
2425
- await appendJsonl(join3(state.artifact_dir, "progress.jsonl"), {
2840
+ const path = runEventsPath(state.id);
2841
+ await ensurePrivateDirectory(dirname4(path));
2842
+ const row = await withAppendLock(`${path}.lock`, async () => {
2843
+ const { partial } = await readJsonlTail(path);
2844
+ const { last: previous } = await readJsonlTail(path, (row2) => typeof row2.hash === "string");
2845
+ if (partial) await appendFile(path, "\n", { encoding: "utf8", mode: 384 });
2846
+ const chained = chainEvent({
2847
+ id: randomUUID4(),
2848
+ run_id: state.id,
2849
+ occurred_at: (/* @__PURE__ */ new Date()).toISOString(),
2850
+ ...event
2851
+ }, previous);
2852
+ await appendJsonl(path, chained);
2853
+ return chained;
2854
+ });
2855
+ await appendJsonl(join4(state.artifact_dir, "progress.jsonl"), {
2426
2856
  at: row.occurred_at,
2427
2857
  stage: row.stage,
2428
2858
  status: row.status,
@@ -2453,8 +2883,8 @@ function createLocalStore(root = defaultLocalHome()) {
2453
2883
  const previous = await getRun(runId);
2454
2884
  if (isTerminalRunState(previous)) return previous;
2455
2885
  await Promise.all([
2456
- rm(join3(paths.modelsDir, `local-${previous.id}`), { recursive: true, force: true }),
2457
- rm(runReportPath(previous.id), { force: true })
2886
+ rm2(join4(paths.modelsDir, `local-${previous.id}`), { recursive: true, force: true }),
2887
+ rm2(runReportPath(previous.id), { force: true })
2458
2888
  ]);
2459
2889
  if (previous.status === "cancelled" && previous.current_stage === "cancel_requested") return previous;
2460
2890
  const requested = {
@@ -2620,7 +3050,7 @@ function createLocalStore(root = defaultLocalHome()) {
2620
3050
  createdAt: completedAt
2621
3051
  });
2622
3052
  if (!model) {
2623
- await rm(join3(paths.modelsDir, `local-${report.run_id}`), { recursive: true, force: true });
3053
+ await rm2(join4(paths.modelsDir, `local-${report.run_id}`), { recursive: true, force: true });
2624
3054
  }
2625
3055
  const previous = await getRun(report.run_id);
2626
3056
  const state = {
@@ -2641,20 +3071,27 @@ function createLocalStore(root = defaultLocalHome()) {
2641
3071
  if (await exists2(cancellationPath(report.run_id))) {
2642
3072
  return preserveCancellationRequestState(report.run_id);
2643
3073
  }
3074
+ const manifestPath = join4(artifactDir, "artifact-manifest.json");
2644
3075
  await appendRunEvent(state, {
2645
3076
  stage: "completed",
2646
3077
  status: "completed",
2647
3078
  message: "Run completed successfully.",
2648
- details: { report_path: reportPath, ...model ? { model_id: model.id } : {} }
3079
+ details: {
3080
+ report_path: reportPath,
3081
+ // Pin the published evidence: `tt runs audit` re-hashes these files.
3082
+ report_sha256: await sha256File2(runReportPath(report.run_id)),
3083
+ ...await exists2(manifestPath) ? { artifact_manifest_sha256: await sha256File2(manifestPath) } : {},
3084
+ ...model ? { model_id: model.id } : {}
3085
+ }
2649
3086
  });
2650
3087
  return state;
2651
3088
  },
2652
3089
  registerModel: registerModelRecord,
2653
3090
  async invalidateRunOutputs(runId, options) {
2654
3091
  const previous = await getRun(runId);
2655
- if (options.report) await rm(runReportPath(previous.id), { force: true });
3092
+ if (options.report) await rm2(runReportPath(previous.id), { force: true });
2656
3093
  if (options.model) {
2657
- await rm(join3(paths.modelsDir, `local-${previous.id}`), { recursive: true, force: true });
3094
+ await rm2(join4(paths.modelsDir, `local-${previous.id}`), { recursive: true, force: true });
2658
3095
  }
2659
3096
  const state = {
2660
3097
  ...previous,
@@ -2697,7 +3134,7 @@ function createLocalStore(root = defaultLocalHome()) {
2697
3134
  `);
2698
3135
  const latest = await getRun(state.id);
2699
3136
  if (isTerminalRunState(latest)) {
2700
- if (latest.status !== "cancelled") await rm(cancellationPath(state.id), { force: true });
3137
+ if (latest.status !== "cancelled") await rm2(cancellationPath(state.id), { force: true });
2701
3138
  return;
2702
3139
  }
2703
3140
  const updated = {
@@ -2724,8 +3161,23 @@ function createLocalStore(root = defaultLocalHome()) {
2724
3161
  },
2725
3162
  getRun,
2726
3163
  async getRunEvents(id) {
3164
+ return (await readRunEventLog(id)).events;
3165
+ },
3166
+ getRunEventLog: readRunEventLog,
3167
+ async getRunReportPaths(id) {
2727
3168
  const state = await getRun(id);
2728
- return readJsonl(runEventsPath(state.id));
3169
+ const candidates = [];
3170
+ if (state.report_path) {
3171
+ const reportPath = resolve6(state.report_path);
3172
+ const allowedRoots = [resolvedRoot, ...state.artifact_dir ? [resolve6(state.artifact_dir)] : []];
3173
+ if (allowedRoots.some((allowed) => isPathInside(allowed, reportPath))) candidates.push(reportPath);
3174
+ }
3175
+ candidates.push(runReportPath(state.id));
3176
+ const existing = [];
3177
+ for (const path of new Set(candidates)) {
3178
+ if ((await lstat3(path).catch(() => null))?.isFile()) existing.push(path);
3179
+ }
3180
+ return existing;
2729
3181
  },
2730
3182
  async getRunReport(id) {
2731
3183
  const state = await getRun(id);
@@ -2756,41 +3208,194 @@ function createLocalStore(root = defaultLocalHome()) {
2756
3208
  }
2757
3209
 
2758
3210
  // src/local-runtime/prefetch.ts
2759
- import { createHash as createHash4 } from "crypto";
2760
- import { createReadStream as createReadStream2 } from "fs";
2761
- import { lstat as lstat4, mkdir as mkdir5, readFile as readFile5, readdir as readdir4, realpath as realpath2, stat as stat4 } from "fs/promises";
2762
- import { isAbsolute as isAbsolute4, join as join6, relative as relative3, resolve as resolve9, sep as sep4 } from "path";
3211
+ import { createHash as createHash5 } from "crypto";
3212
+ import { createReadStream as createReadStream3 } from "fs";
3213
+ import { lstat as lstat4, mkdir as mkdir6, readFile as readFile6, readdir as readdir4, realpath as realpath2, stat as stat5 } from "fs/promises";
3214
+ import { isAbsolute as isAbsolute4, join as join7, relative as relative3, resolve as resolve9, sep as sep4 } from "path";
2763
3215
 
2764
3216
  // src/local-runtime/process-runner.ts
2765
- import { createHash as createHash3 } from "crypto";
3217
+ import { createHash as createHash4 } from "crypto";
2766
3218
  import { spawn } from "child_process";
2767
3219
  import { constants, readFileSync } from "fs";
2768
- import { mkdir as mkdir4, open as open2 } from "fs/promises";
2769
- import { dirname as dirname5, join as join4, resolve as resolve7 } from "path";
3220
+ import { mkdir as mkdir5, open as open3 } from "fs/promises";
3221
+ import { dirname as dirname5, join as join5, resolve as resolve7 } from "path";
3222
+
3223
+ // src/local-runtime/progress-bar.ts
3224
+ var PROGRESS_PREFIX = "@@tt-progress ";
3225
+ function nonNegativeInteger(value) {
3226
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : null;
3227
+ }
3228
+ function parseProgressLine(line) {
3229
+ const start = line.indexOf(PROGRESS_PREFIX);
3230
+ if (start !== 0) return null;
3231
+ let value;
3232
+ try {
3233
+ value = JSON.parse(line.slice(PROGRESS_PREFIX.length));
3234
+ } catch {
3235
+ return null;
3236
+ }
3237
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3238
+ const record = value;
3239
+ const completed = nonNegativeInteger(record.completed_bytes);
3240
+ const total = nonNegativeInteger(record.total_bytes);
3241
+ if (completed === null || total === null) return null;
3242
+ return {
3243
+ label: typeof record.label === "string" ? record.label : "download",
3244
+ phase: typeof record.phase === "string" ? record.phase : "download",
3245
+ completed_bytes: completed,
3246
+ total_bytes: total,
3247
+ files_completed: nonNegativeInteger(record.files_completed) ?? 0,
3248
+ files_total: nonNegativeInteger(record.files_total) ?? 0
3249
+ };
3250
+ }
3251
+ function formatBytes(bytes) {
3252
+ const units = ["B", "KiB", "MiB", "GiB", "TiB"];
3253
+ let value = bytes;
3254
+ let unit = 0;
3255
+ while (value >= 1024 && unit < units.length - 1) {
3256
+ value /= 1024;
3257
+ unit += 1;
3258
+ }
3259
+ return `${unit === 0 ? value.toFixed(0) : value.toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2)} ${units[unit]}`;
3260
+ }
3261
+ function formatDuration(seconds) {
3262
+ if (!Number.isFinite(seconds) || seconds < 0) return "--";
3263
+ const whole = Math.round(seconds);
3264
+ const hours = Math.floor(whole / 3600);
3265
+ const minutes = Math.floor(whole % 3600 / 60);
3266
+ const rest = whole % 60;
3267
+ if (hours > 0) return `${hours}h ${String(minutes).padStart(2, "0")}m`;
3268
+ if (minutes > 0) return `${minutes}m ${String(rest).padStart(2, "0")}s`;
3269
+ return `${rest}s`;
3270
+ }
3271
+ var RateTracker = class {
3272
+ constructor(windowMs = 15e3) {
3273
+ this.windowMs = windowMs;
3274
+ }
3275
+ samples = [];
3276
+ add(at, bytes) {
3277
+ const last = this.samples.at(-1);
3278
+ if (last && bytes < last.bytes) this.samples = [];
3279
+ this.samples.push({ at, bytes });
3280
+ while (this.samples.length > 2 && at - this.samples[0].at > this.windowMs) this.samples.shift();
3281
+ }
3282
+ bytesPerSecond() {
3283
+ const first = this.samples[0];
3284
+ const last = this.samples.at(-1);
3285
+ if (!first || !last || last.at - first.at < 500) return null;
3286
+ return (last.bytes - first.bytes) / ((last.at - first.at) / 1e3);
3287
+ }
3288
+ };
3289
+ function formatProgressLine(progress, rate, columns) {
3290
+ const total = progress.total_bytes;
3291
+ const fraction = total > 0 ? Math.min(1, progress.completed_bytes / total) : 1;
3292
+ const fields = [
3293
+ { text: `${(fraction * 100).toFixed(1).padStart(5)}%` },
3294
+ { text: `${formatBytes(progress.completed_bytes)} / ${formatBytes(total)}` }
3295
+ ];
3296
+ const done = progress.completed_bytes >= total;
3297
+ if (!done && rate !== null && rate > 0) {
3298
+ fields.push(
3299
+ { text: `${formatBytes(rate)}/s`, drop: 2 },
3300
+ { text: `ETA ${formatDuration((total - progress.completed_bytes) / rate)}`, drop: 3 }
3301
+ );
3302
+ }
3303
+ if (progress.files_total > 0) fields.push({ text: `${progress.files_completed}/${progress.files_total} files`, drop: 1 });
3304
+ const prefix = `[tt] ${progress.label}: `;
3305
+ const join16 = () => fields.map((field) => field.text).join(" ");
3306
+ if (columns === void 0) return `${prefix}${join16()}`;
3307
+ for (const rank of [1, 2, 3]) {
3308
+ if (prefix.length + join16().length <= columns - 1) break;
3309
+ const index = fields.findIndex((field) => field.drop === rank);
3310
+ if (index !== -1) fields.splice(index, 1);
3311
+ }
3312
+ const text = join16();
3313
+ const barWidth = Math.min(30, columns - 1 - prefix.length - text.length - 3);
3314
+ if (barWidth < 10) return `${prefix}${text}`.slice(0, Math.max(0, columns - 1));
3315
+ const filled = Math.round(fraction * barWidth);
3316
+ return `${prefix}[${"#".repeat(filled)}${"-".repeat(barWidth - filled)}] ${text}`;
3317
+ }
3318
+ function createProgressRenderer(options) {
3319
+ const now = options.now ?? (() => Date.now());
3320
+ const rates = /* @__PURE__ */ new Map();
3321
+ const lastLine = /* @__PURE__ */ new Map();
3322
+ const completed = /* @__PURE__ */ new Set();
3323
+ let drawn = false;
3324
+ let lastDraw = Number.NEGATIVE_INFINITY;
3325
+ const finished = (progress) => progress.phase === "downloaded" || progress.total_bytes > 0 && progress.completed_bytes >= progress.total_bytes;
3326
+ return {
3327
+ update(progress) {
3328
+ const at = now();
3329
+ const tracker = rates.get(progress.label) ?? new RateTracker();
3330
+ rates.set(progress.label, tracker);
3331
+ tracker.add(at, progress.completed_bytes);
3332
+ const done = finished(progress);
3333
+ if (options.tty) {
3334
+ if (done && completed.has(progress.label)) return;
3335
+ if (!done) completed.delete(progress.label);
3336
+ if (!done && at - lastDraw < 100) return;
3337
+ lastDraw = at;
3338
+ const line = formatProgressLine(progress, tracker.bytesPerSecond(), Math.max(40, options.columns ?? 80));
3339
+ options.write(`\r\x1B[2K${line}${done ? "\n" : ""}`);
3340
+ drawn = !done;
3341
+ if (done) {
3342
+ rates.delete(progress.label);
3343
+ completed.add(progress.label);
3344
+ }
3345
+ return;
3346
+ }
3347
+ const fraction = progress.total_bytes > 0 ? progress.completed_bytes / progress.total_bytes : 1;
3348
+ const decile = Math.floor(fraction * 10);
3349
+ const previous = lastLine.get(progress.label);
3350
+ const interval = options.lineIntervalMs ?? 3e4;
3351
+ if (previous && !done && decile <= previous.decile && at - previous.at < interval) return;
3352
+ if (previous && done && previous.decile === 10) return;
3353
+ lastLine.set(progress.label, { at, decile: done ? 10 : decile });
3354
+ options.write(`${formatProgressLine(progress, tracker.bytesPerSecond())}
3355
+ `);
3356
+ },
3357
+ interrupt() {
3358
+ if (!drawn) return;
3359
+ options.write("\r\x1B[2K");
3360
+ drawn = false;
3361
+ }
3362
+ };
3363
+ }
3364
+ function progressTty(env = process.env, stream = process.stderr) {
3365
+ if (env.TT_PROGRESS_TTY === "0") return false;
3366
+ return env.TT_PROGRESS_TTY === "1" || Boolean(stream.isTTY);
3367
+ }
3368
+ function progressColumns(env = process.env, stream = process.stderr) {
3369
+ const hinted = Number(env.TT_PROGRESS_COLUMNS);
3370
+ if (Number.isInteger(hinted) && hinted > 0) return hinted;
3371
+ return stream.columns;
3372
+ }
3373
+
3374
+ // src/local-runtime/process-runner.ts
2770
3375
  var packageRoot = localRuntimePackageRoot(import.meta.url);
2771
- var bundledProject = join4(packageRoot, "training/adapter");
3376
+ var bundledProject = join5(packageRoot, "training/adapter");
2772
3377
  var bundledRuntimeHash = (() => {
2773
- const hash = createHash3("sha256");
3378
+ const hash = createHash4("sha256");
2774
3379
  for (const name of ["pyproject.toml", "uv.lock"]) {
2775
- hash.update(readFileSync(join4(bundledProject, name)));
3380
+ hash.update(readFileSync(join5(bundledProject, name)));
2776
3381
  }
2777
3382
  return hash.digest("hex").slice(0, 20);
2778
3383
  })();
2779
3384
  var BUNDLED_PYTHON_ENVIRONMENT = pythonEnvironmentPath("uv", bundledRuntimeHash);
2780
- var foundationProject = join4(packageRoot, "training/foundation");
3385
+ var foundationProject = join5(packageRoot, "training/foundation");
2781
3386
  var foundationRuntimeHash = (() => {
2782
- const hash = createHash3("sha256");
3387
+ const hash = createHash4("sha256");
2783
3388
  for (const name of ["pyproject.toml", "uv.lock"]) {
2784
- hash.update(readFileSync(join4(foundationProject, name)));
3389
+ hash.update(readFileSync(join5(foundationProject, name)));
2785
3390
  }
2786
3391
  return hash.digest("hex").slice(0, 20);
2787
3392
  })();
2788
3393
  var FOUNDATION_PYTHON_ENVIRONMENT = pythonEnvironmentPath("uv-foundation", foundationRuntimeHash);
2789
- var decisionProject = join4(packageRoot, "training/decision");
3394
+ var decisionProject = join5(packageRoot, "training/decision");
2790
3395
  var decisionRuntimeHash = (() => {
2791
- const hash = createHash3("sha256");
3396
+ const hash = createHash4("sha256");
2792
3397
  for (const name of ["pyproject.toml", "uv.lock"]) {
2793
- hash.update(readFileSync(join4(decisionProject, name)));
3398
+ hash.update(readFileSync(join5(decisionProject, name)));
2794
3399
  }
2795
3400
  return hash.digest("hex").slice(0, 20);
2796
3401
  })();
@@ -2803,7 +3408,7 @@ function buildDecisionPythonCommand(entrypoint, args = []) {
2803
3408
  "--project",
2804
3409
  decisionProject,
2805
3410
  "python",
2806
- join4(decisionProject, "src", entrypoint),
3411
+ join5(decisionProject, "src", entrypoint),
2807
3412
  ...args
2808
3413
  ];
2809
3414
  return { command: "uv", commandArgs, displayCommand: ["uv", ...commandArgs] };
@@ -2812,7 +3417,7 @@ function withDecisionPythonEnvironment(env) {
2812
3417
  return { ...env, UV_PROJECT_ENVIRONMENT: DECISION_PYTHON_ENVIRONMENT };
2813
3418
  }
2814
3419
  function buildBundledPythonCommand(entrypoint, args = []) {
2815
- const target = entrypoint === "-c" ? entrypoint : join4(bundledProject, "src", entrypoint);
3420
+ const target = entrypoint === "-c" ? entrypoint : join5(bundledProject, "src", entrypoint);
2816
3421
  const commandArgs = [
2817
3422
  "run",
2818
3423
  "--frozen",
@@ -2836,7 +3441,7 @@ function withBundledPythonEnvironment(env) {
2836
3441
  };
2837
3442
  }
2838
3443
  function buildFoundationPythonCommand(entrypoint, args = []) {
2839
- const target = entrypoint === "-c" ? entrypoint : join4(foundationProject, "src", entrypoint);
3444
+ const target = entrypoint === "-c" ? entrypoint : join5(foundationProject, "src", entrypoint);
2840
3445
  const commandArgs = [
2841
3446
  "run",
2842
3447
  "--frozen",
@@ -2867,7 +3472,7 @@ var ProcessCancelledError = class extends Error {
2867
3472
  };
2868
3473
  async function openProcessLog(path, exclusive, append) {
2869
3474
  const flags = constants.O_WRONLY | constants.O_CREAT | (exclusive ? constants.O_EXCL : append ? constants.O_APPEND : constants.O_TRUNC) | (process.platform === "win32" ? 0 : constants.O_NOFOLLOW);
2870
- const handle = await open2(path, flags, 384);
3475
+ const handle = await open3(path, flags, 384);
2871
3476
  try {
2872
3477
  return handle.createWriteStream({ autoClose: true });
2873
3478
  } catch (error) {
@@ -2876,7 +3481,7 @@ async function openProcessLog(path, exclusive, append) {
2876
3481
  }
2877
3482
  }
2878
3483
  async function runLoggedProcess(args) {
2879
- if (args.logPath) await mkdir4(dirname5(args.logPath), { recursive: true });
3484
+ if (args.logPath) await mkdir5(dirname5(args.logPath), { recursive: true });
2880
3485
  const logStream = args.logPath ? await openProcessLog(args.logPath, args.exclusiveLog ?? false, args.appendLog ?? false) : null;
2881
3486
  let stderr = "";
2882
3487
  try {
@@ -2950,6 +3555,11 @@ async function runLoggedProcess(args) {
2950
3555
  child.stderr.pipe(logStream, { end: false });
2951
3556
  }
2952
3557
  forwardStreamLines(child.stdout, (line) => {
3558
+ const progress = parseProgressLine(line);
3559
+ if (progress) {
3560
+ reportInBackground(() => args.reporter?.onProgress?.(progress));
3561
+ return;
3562
+ }
2953
3563
  args.onLine?.(line, "stdout");
2954
3564
  if (args.reporter?.verbose) {
2955
3565
  reportInBackground(() => args.reporter?.onLog?.({ stage: args.stage, stream: "stdout", message: line }));
@@ -2999,7 +3609,7 @@ async function runLoggedProcess(args) {
2999
3609
 
3000
3610
  // src/local-runtime/huggingface-cache.ts
3001
3611
  import { homedir as homedir3 } from "os";
3002
- import { dirname as dirname6, join as join5, resolve as resolve8 } from "path";
3612
+ import { dirname as dirname6, join as join6, resolve as resolve8 } from "path";
3003
3613
  function minimalMachineLearningEnvironment(env) {
3004
3614
  const exact = /* @__PURE__ */ new Set([
3005
3615
  "PATH",
@@ -3031,6 +3641,9 @@ function minimalMachineLearningEnvironment(env) {
3031
3641
  "HF_HUB_DISABLE_TELEMETRY",
3032
3642
  "TRANSFORMERS_OFFLINE",
3033
3643
  "HF_HUB_ENABLE_HF_TRANSFER",
3644
+ "HF_HUB_DOWNLOAD_TIMEOUT",
3645
+ "HF_HUB_ETAG_TIMEOUT",
3646
+ "TT_HF_DOWNLOAD_MAX_ATTEMPTS",
3034
3647
  "PYTHONUTF8",
3035
3648
  "PYTHONIOENCODING"
3036
3649
  ]);
@@ -3056,12 +3669,12 @@ function resolveHuggingFaceCacheLayout(modelCache) {
3056
3669
  const hfHome = resolve8(modelCache);
3057
3670
  return {
3058
3671
  hfHome,
3059
- hubCache: join5(hfHome, "hub")
3672
+ hubCache: join6(hfHome, "hub")
3060
3673
  };
3061
3674
  }
3062
3675
  function withHuggingFaceCacheEnvironment(env, modelCache) {
3063
3676
  const inheritedHub = env.HF_HUB_CACHE ?? env.HUGGINGFACE_HUB_CACHE;
3064
- const canonicalHome = modelCache ?? env.HF_HOME ?? (inheritedHub ? dirname6(resolve8(inheritedHub)) : join5(homedir3(), ".cache", "huggingface"));
3677
+ const canonicalHome = modelCache ?? env.HF_HOME ?? (inheritedHub ? dirname6(resolve8(inheritedHub)) : join6(homedir3(), ".cache", "huggingface"));
3065
3678
  const layout = resolveHuggingFaceCacheLayout(canonicalHome);
3066
3679
  const result = {
3067
3680
  ...env,
@@ -3141,10 +3754,10 @@ function certifiedFileDigests(modelId) {
3141
3754
  }
3142
3755
  }
3143
3756
  async function certifiedFileDigest(path, expected) {
3144
- const metadata = await stat4(path);
3145
- const digest = createHash4(expected.algorithm);
3757
+ const metadata = await stat5(path);
3758
+ const digest = createHash5(expected.algorithm);
3146
3759
  if (expected.algorithm === "sha1") digest.update(`blob ${metadata.size}\0`);
3147
- for await (const chunk of createReadStream2(path)) digest.update(chunk);
3760
+ for await (const chunk of createReadStream3(path)) digest.update(chunk);
3148
3761
  return digest.digest("hex");
3149
3762
  }
3150
3763
  function isStrictPhysicalDescendant(parent, child) {
@@ -3154,8 +3767,8 @@ function isStrictPhysicalDescendant(parent, child) {
3154
3767
  async function verifySnapshotCacheLocation(path, modelId, revision, modelCache) {
3155
3768
  const repository = `models--${modelId.replaceAll("/", "--")}`;
3156
3769
  const layout = resolveHuggingFaceCacheLayout(modelCache);
3157
- const repositoryRoot = join6(layout.hubCache, repository);
3158
- const expectedPath = join6(repositoryRoot, "snapshots", revision);
3770
+ const repositoryRoot = join7(layout.hubCache, repository);
3771
+ const expectedPath = join7(repositoryRoot, "snapshots", revision);
3159
3772
  const [
3160
3773
  physicalCacheRoot,
3161
3774
  physicalHubRoot,
@@ -3167,7 +3780,7 @@ async function verifySnapshotCacheLocation(path, modelId, revision, modelCache)
3167
3780
  realpath2(layout.hfHome).catch(() => null),
3168
3781
  realpath2(layout.hubCache).catch(() => null),
3169
3782
  realpath2(repositoryRoot).catch(() => null),
3170
- realpath2(join6(repositoryRoot, "blobs")).catch(() => null),
3783
+ realpath2(join7(repositoryRoot, "blobs")).catch(() => null),
3171
3784
  realpath2(path).catch(() => null),
3172
3785
  realpath2(expectedPath).catch(() => null)
3173
3786
  ]);
@@ -3183,7 +3796,7 @@ async function verifySnapshotCacheLocation(path, modelId, revision, modelCache)
3183
3796
  throw new Error(`Hugging Face repository escapes its configured hub cache: ${repositoryRoot}`);
3184
3797
  }
3185
3798
  if (!isStrictPhysicalDescendant(physicalRepositoryRoot, physicalBlobRoot)) {
3186
- throw new Error(`Hugging Face blob store escapes its repository: ${join6(repositoryRoot, "blobs")}`);
3799
+ throw new Error(`Hugging Face blob store escapes its repository: ${join7(repositoryRoot, "blobs")}`);
3187
3800
  }
3188
3801
  if (!isStrictPhysicalDescendant(physicalRepositoryRoot, physicalExpectedPath)) {
3189
3802
  throw new Error(`Hugging Face snapshot escapes its configured repository: ${expectedPath}`);
@@ -3245,13 +3858,13 @@ async function verifyCertifiedLocalSnapshot(path, modelId, snapshotFiles, modelC
3245
3858
  const expectedFiles = new Set(Object.keys(digests));
3246
3859
  const unexpected = [...actualFiles].filter((name) => !expectedFiles.has(name)).sort();
3247
3860
  if (unexpected.length > 0) {
3248
- throw new Error(`Certified snapshot contains unexpected file: ${join6(path, unexpected[0])}`);
3861
+ throw new Error(`Certified snapshot contains unexpected file: ${join7(path, unexpected[0])}`);
3249
3862
  }
3250
3863
  for (const [name, expected] of Object.entries(digests)) {
3251
3864
  if (!actualFiles.has(name)) {
3252
- throw new Error(`Certified snapshot file is missing: ${join6(path, name)}`);
3865
+ throw new Error(`Certified snapshot file is missing: ${join7(path, name)}`);
3253
3866
  }
3254
- const file = join6(path, name);
3867
+ const file = join7(path, name);
3255
3868
  const physicalFile = physicalFiles.get(file);
3256
3869
  if (await certifiedFileDigest(physicalFile, expected) !== expected.digest) {
3257
3870
  throw new Error(`Certified snapshot file checksum mismatch: ${file}`);
@@ -3294,9 +3907,9 @@ async function verifyLocalBaseModel(path, expectedModelId, modelCache) {
3294
3907
  const files = [];
3295
3908
  const visit = async (directory) => {
3296
3909
  for (const entry of await readdir4(directory, { withFileTypes: true })) {
3297
- const child = join6(directory, entry.name);
3910
+ const child = join7(directory, entry.name);
3298
3911
  if (entry.isSymbolicLink()) {
3299
- const metadata2 = await stat4(child);
3912
+ const metadata2 = await stat5(child);
3300
3913
  if (!metadata2.isFile()) {
3301
3914
  throw new Error(`Local base model contains a non-file symbolic link: ${child}`);
3302
3915
  }
@@ -3307,7 +3920,7 @@ async function verifyLocalBaseModel(path, expectedModelId, modelCache) {
3307
3920
  await visit(child);
3308
3921
  continue;
3309
3922
  }
3310
- const metadata = await stat4(child);
3923
+ const metadata = await stat5(child);
3311
3924
  if (metadata.isFile()) files.push({ path: child, size: metadata.size });
3312
3925
  }
3313
3926
  };
@@ -3318,15 +3931,15 @@ async function verifyLocalBaseModel(path, expectedModelId, modelCache) {
3318
3931
  throw new Error(`Local base-model directory contains no non-empty Transformers model weights: ${path}`);
3319
3932
  }
3320
3933
  const required = async (name) => {
3321
- const metadata = await stat4(join6(path, name)).catch(() => null);
3934
+ const metadata = await stat5(join7(path, name)).catch(() => null);
3322
3935
  return Boolean(metadata?.isFile() && metadata.size > 0);
3323
3936
  };
3324
3937
  if (!await required("config.json")) throw new Error(`Local base-model directory is missing config.json: ${path}`);
3325
3938
  try {
3326
- const config = JSON.parse(await readFile5(join6(path, "config.json"), "utf8"));
3939
+ const config = JSON.parse(await readFile6(join7(path, "config.json"), "utf8"));
3327
3940
  assertCertifiedBaseModelConfig(
3328
3941
  config,
3329
- `Local base-model config ${join6(path, "config.json")}`,
3942
+ `Local base-model config ${join7(path, "config.json")}`,
3330
3943
  expectedModelId
3331
3944
  );
3332
3945
  } catch (error) {
@@ -3355,13 +3968,17 @@ async function verifyLocalBaseModel(path, expectedModelId, modelCache) {
3355
3968
  if (!await required("tokenizer_config.json") || !(await Promise.all(vocabNames.map(required))).some(Boolean)) {
3356
3969
  throw new Error(`Local base-model directory is missing tokenizer metadata or vocabulary: ${path}`);
3357
3970
  }
3971
+ const inventory = new Set(nonEmpty.map((file) => resolve9(file.path)));
3358
3972
  for (const file of files.filter((candidate) => candidate.path.endsWith(".index.json"))) {
3359
- const parsed = JSON.parse(await readFile5(file.path, "utf8"));
3973
+ const parsed = JSON.parse(await readFile6(file.path, "utf8"));
3360
3974
  if (!parsed.weight_map || typeof parsed.weight_map !== "object" || Array.isArray(parsed.weight_map)) {
3361
3975
  throw new Error(`Local base-model weight index is invalid: ${file.path}`);
3362
3976
  }
3363
3977
  for (const shard of new Set(Object.values(parsed.weight_map))) {
3364
- if (typeof shard !== "string" || !await required(shard)) {
3978
+ if (typeof shard !== "string" || !shard || /^(?:[A-Za-z]:|[/\\])/.test(shard) || shard.split(/[/\\]/).includes("..")) {
3979
+ throw new Error("Local base-model weight index has an invalid indexed weight shard path");
3980
+ }
3981
+ if (!inventory.has(resolve9(path, shard)) || !await required(shard)) {
3365
3982
  throw new Error(`Local base-model directory is missing indexed weight shard: ${String(shard)}`);
3366
3983
  }
3367
3984
  }
@@ -3381,7 +3998,7 @@ async function prefetchBaseModel(args) {
3381
3998
  args.localOnly ? "verify-base" : "prefetch",
3382
3999
  `${safeName(args.request.spec_snapshot.base_model)}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`
3383
4000
  );
3384
- await mkdir5(artifactDir, { recursive: true });
4001
+ await mkdir6(artifactDir, { recursive: true });
3385
4002
  if (args.config.paths.baseModel) {
3386
4003
  const localPath = resolve9(args.config.paths.baseModel);
3387
4004
  const verified = await verifyLocalBaseModel(
@@ -3400,9 +4017,9 @@ async function prefetchBaseModel(args) {
3400
4017
  reason: args.localOnly ? "Verified the configured local base-model artifact; no network access was used." : "paths.baseModel points at a verified local base-model artifact; no Hugging Face download is needed."
3401
4018
  };
3402
4019
  }
3403
- const inputPath = join6(artifactDir, "prefetch-input.json");
3404
- const outputPath = join6(artifactDir, "prefetch-output.json");
3405
- const logPath = join6(artifactDir, "prefetch.log");
4020
+ const inputPath = join7(artifactDir, "prefetch-input.json");
4021
+ const outputPath = join7(artifactDir, "prefetch-output.json");
4022
+ const logPath = join7(artifactDir, "prefetch.log");
3406
4023
  await writeJson(inputPath, payload);
3407
4024
  const entrypoint = buildBundledPythonCommand(
3408
4025
  "prefetch.py",
@@ -3439,7 +4056,7 @@ async function prefetchBaseModel(args) {
3439
4056
  if (exitCode !== 0) {
3440
4057
  throw new Error(`Model prefetch exited with code ${exitCode}. See ${logPath}.`);
3441
4058
  }
3442
- const output = JSON.parse(await readFile5(outputPath, "utf8"));
4059
+ const output = JSON.parse(await readFile6(outputPath, "utf8"));
3443
4060
  const snapshotRevision = baseModelRevisionSchema.safeParse(output.snapshot_revision);
3444
4061
  if (!snapshotRevision.success) {
3445
4062
  throw new Error(
@@ -3502,6 +4119,346 @@ async function prefetchBaseModel(args) {
3502
4119
  };
3503
4120
  }
3504
4121
 
4122
+ // src/local-runtime/dataset-prefetch.ts
4123
+ import { createHash as createHash6 } from "crypto";
4124
+ import { createReadStream as createReadStream4 } from "fs";
4125
+ import { mkdir as mkdir7, readFile as readFile7, realpath as realpath3, stat as stat6 } from "fs/promises";
4126
+ import { createInterface } from "readline";
4127
+ import { basename, isAbsolute as isAbsolute5, join as join8, relative as relative4, resolve as resolve10, sep as sep5 } from "path";
4128
+ var SPLITS = ["training", "validation", "test"];
4129
+ var DatasetNotDownloadedError = class extends Error {
4130
+ constructor(message) {
4131
+ super(message);
4132
+ this.name = "DatasetNotDownloadedError";
4133
+ }
4134
+ };
4135
+ function huggingFaceDataset(request) {
4136
+ const dataset = request.dataset_prebuilt;
4137
+ return dataset?.huggingface ? dataset : void 0;
4138
+ }
4139
+ function datasetFiles(dataset) {
4140
+ const files = {};
4141
+ for (const split of SPLITS) if (dataset[split]) files[split] = dataset[split];
4142
+ return files;
4143
+ }
4144
+ function huggingFaceHubCache(modelCache) {
4145
+ return withHuggingFaceCacheEnvironment(process.env, modelCache).HF_HUB_CACHE;
4146
+ }
4147
+ function repositoryDirectory(hubCache, repo) {
4148
+ return join8(hubCache, `datasets--${repo.replaceAll("/", "--")}`);
4149
+ }
4150
+ function isStrictDescendant(parent, child) {
4151
+ const path = relative4(parent, child);
4152
+ return path !== "" && path !== ".." && !path.startsWith(`..${sep5}`) && !isAbsolute5(path);
4153
+ }
4154
+ async function digestFile(path, size) {
4155
+ const sha256 = createHash6("sha256");
4156
+ const gitSha1 = createHash6("sha1").update(`blob ${size}\0`);
4157
+ for await (const chunk of createReadStream4(path)) {
4158
+ sha256.update(chunk);
4159
+ gitSha1.update(chunk);
4160
+ }
4161
+ return { sha256: sha256.digest("hex"), gitSha1: gitSha1.digest("hex") };
4162
+ }
4163
+ function describe(dataset) {
4164
+ return `${dataset.repo}@${dataset.revision ?? "main"}`;
4165
+ }
4166
+ async function resolveCachedHuggingFaceDataset(args) {
4167
+ const source = args.dataset.huggingface;
4168
+ const hubCache = huggingFaceHubCache(args.modelCache);
4169
+ const repository = repositoryDirectory(hubCache, source.repo);
4170
+ const prefetchHint = "Run `tt datasets prefetch` with this spec (for example `tt datasets prefetch tunedtensor.json`) to download it.";
4171
+ let revision = source.revision;
4172
+ if (!revision) {
4173
+ const ref = await readFile7(join8(repository, "refs", "main"), "utf8").catch(() => null);
4174
+ const parsed = baseModelRevisionSchema.safeParse(ref?.trim());
4175
+ if (!parsed.success) {
4176
+ throw new DatasetNotDownloadedError(
4177
+ `Hugging Face dataset ${describe(source)} is not in the local cache (${hubCache}). ${prefetchHint}`
4178
+ );
4179
+ }
4180
+ revision = parsed.data;
4181
+ }
4182
+ const snapshot = join8(repository, "snapshots", revision);
4183
+ const physicalRepository = await realpath3(repository).catch(() => null);
4184
+ const blobs = physicalRepository ? join8(physicalRepository, "blobs") : null;
4185
+ const files = {};
4186
+ const localPaths = {};
4187
+ for (const [split, path] of Object.entries(datasetFiles(args.dataset))) {
4188
+ const local = join8(snapshot, ...path.split(/[/\\]/));
4189
+ const metadata = await stat6(local).catch(() => null);
4190
+ if (!metadata || !physicalRepository || !blobs) {
4191
+ throw new DatasetNotDownloadedError(
4192
+ `Hugging Face dataset file ${path} (${split}) from ${source.repo}@${revision} is not in the local cache (${hubCache}). ${prefetchHint}`
4193
+ );
4194
+ }
4195
+ const target = await realpath3(local);
4196
+ if (!metadata.isFile() || !isStrictDescendant(physicalRepository, target)) {
4197
+ throw new Error(`Cached dataset file ${local} must resolve inside its Hugging Face cache repository ${physicalRepository}.`);
4198
+ }
4199
+ if (metadata.size === 0) throw new Error(`Cached dataset file ${path} (${split}) is empty: ${local}`);
4200
+ const digests = await digestFile(target, metadata.size);
4201
+ const blobName = isStrictDescendant(blobs, target) ? basename(target).toLowerCase() : "";
4202
+ const expected = /^[0-9a-f]{64}$/.test(blobName) ? digests.sha256 : /^[0-9a-f]{40}$/.test(blobName) ? digests.gitSha1 : null;
4203
+ if (expected !== null && expected !== blobName) {
4204
+ throw new Error(
4205
+ `Cached dataset file ${path} (${split}) failed its checksum (${local}). Delete ${target} and run \`tt datasets prefetch\` again.`
4206
+ );
4207
+ }
4208
+ files[split] = { path, size_bytes: metadata.size, sha256: digests.sha256 };
4209
+ localPaths[split] = local;
4210
+ }
4211
+ return {
4212
+ source: datasetSourceSchema.parse({
4213
+ provider: "huggingface",
4214
+ repo: source.repo,
4215
+ requested_revision: source.revision ?? null,
4216
+ revision,
4217
+ pinned: Boolean(source.revision),
4218
+ columns: source.columns ?? null,
4219
+ snapshot_path: snapshot,
4220
+ files
4221
+ }),
4222
+ localPaths
4223
+ };
4224
+ }
4225
+ function mappedField(row, name) {
4226
+ const value = row[name];
4227
+ if (typeof value === "string") return value.trim() ? value : null;
4228
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
4229
+ return null;
4230
+ }
4231
+ async function readMappedRecords(path, split, columns) {
4232
+ const examples = [];
4233
+ let records = 0;
4234
+ let skipped = 0;
4235
+ const availableFields = /* @__PURE__ */ new Set();
4236
+ const lines = createInterface({ input: createReadStream4(path, { encoding: "utf8" }), crlfDelay: Infinity });
4237
+ let index = -1;
4238
+ for await (const line of lines) {
4239
+ index += 1;
4240
+ if (!line.trim()) continue;
4241
+ records += 1;
4242
+ let value;
4243
+ try {
4244
+ value = JSON.parse(line);
4245
+ } catch (error) {
4246
+ throw new Error(`${split} line ${index + 1}: malformed JSON in ${path}`, { cause: error });
4247
+ }
4248
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
4249
+ throw new Error(`${split} line ${index + 1}: expected a JSON object in ${path}`);
4250
+ }
4251
+ const row = value;
4252
+ for (const name of Object.keys(row)) availableFields.add(name);
4253
+ const input = mappedField(row, columns.input);
4254
+ const output = mappedField(row, columns.output);
4255
+ if (input === null || output === null) {
4256
+ skipped += 1;
4257
+ continue;
4258
+ }
4259
+ examples.push({ input, output });
4260
+ }
4261
+ if (records > 0) {
4262
+ for (const name of [columns.input, columns.output]) {
4263
+ if (!availableFields.has(name)) {
4264
+ throw new Error(
4265
+ `${split} records have no "${name}" field (dataset_prebuilt.huggingface.columns). Available fields: ${[...availableFields].join(", ")}`
4266
+ );
4267
+ }
4268
+ }
4269
+ }
4270
+ return { examples, records, skipped };
4271
+ }
4272
+ async function convertMappedDataset(args) {
4273
+ const system = buildSystemMessage(args.spec);
4274
+ const splits = Object.keys(args.resolved.localPaths);
4275
+ const key = createHash6("sha256").update(JSON.stringify({
4276
+ version: 1,
4277
+ sources: splits.map((split) => [split, args.resolved.source.files[split]?.sha256]),
4278
+ columns: args.columns,
4279
+ system
4280
+ })).digest("hex").slice(0, 16);
4281
+ const loaded = /* @__PURE__ */ new Map();
4282
+ for (const split of splits) {
4283
+ loaded.set(split, await readMappedRecords(args.resolved.localPaths[split], split, args.columns));
4284
+ }
4285
+ const evaluationInputs = /* @__PURE__ */ new Set();
4286
+ const kept = /* @__PURE__ */ new Map();
4287
+ const conversion = {};
4288
+ for (const split of splits.filter((name) => name !== "training")) {
4289
+ const seen = /* @__PURE__ */ new Set();
4290
+ const data = loaded.get(split);
4291
+ const examples = data.examples.filter((example) => {
4292
+ const identity = datasetInputIdentity(example.input);
4293
+ if (seen.has(identity)) return false;
4294
+ seen.add(identity);
4295
+ evaluationInputs.add(identity);
4296
+ return true;
4297
+ });
4298
+ kept.set(split, examples);
4299
+ conversion[split] = {
4300
+ records: data.records,
4301
+ kept: examples.length,
4302
+ skipped_missing_fields: data.skipped,
4303
+ dropped_duplicate_inputs: data.examples.length - examples.length,
4304
+ dropped_eval_overlap: 0
4305
+ };
4306
+ }
4307
+ const training = loaded.get("training");
4308
+ const trainingExamples = training.examples.filter((example) => !evaluationInputs.has(datasetInputIdentity(example.input)));
4309
+ kept.set("training", trainingExamples);
4310
+ conversion.training = {
4311
+ records: training.records,
4312
+ kept: trainingExamples.length,
4313
+ skipped_missing_fields: training.skipped,
4314
+ dropped_duplicate_inputs: 0,
4315
+ dropped_eval_overlap: training.examples.length - trainingExamples.length
4316
+ };
4317
+ await mkdir7(args.outputRoot, { recursive: true });
4318
+ const localPaths = {};
4319
+ for (const split of splits) {
4320
+ const examples = kept.get(split);
4321
+ if (examples.length === 0) {
4322
+ throw new Error(`No usable ${split} records remain after applying dataset_prebuilt.huggingface.columns.`);
4323
+ }
4324
+ const output = join8(args.outputRoot, `${split}-${key}.jsonl`);
4325
+ const content = `${examples.map((example) => JSON.stringify(exampleToChatRow(args.spec, example))).join("\n")}
4326
+ `;
4327
+ if (await readFile7(output, "utf8").catch(() => null) !== content) {
4328
+ await writeFileAtomic(output, content);
4329
+ }
4330
+ localPaths[split] = output;
4331
+ }
4332
+ return { localPaths, conversion };
4333
+ }
4334
+ async function resolveRequestDataset(request, config) {
4335
+ const dataset = huggingFaceDataset(request);
4336
+ if (!dataset) return request;
4337
+ const resolved = await resolveCachedHuggingFaceDataset({ dataset, modelCache: config.paths.modelCache });
4338
+ let localPaths = resolved.localPaths;
4339
+ let source = resolved.source;
4340
+ const columns = dataset.huggingface.columns;
4341
+ if (columns) {
4342
+ const converted = await convertMappedDataset({
4343
+ resolved,
4344
+ columns,
4345
+ spec: request.spec_snapshot,
4346
+ outputRoot: resolve10(config.artifactRoot, "datasets", "huggingface", safeName(dataset.huggingface.repo), resolved.source.revision)
4347
+ });
4348
+ localPaths = converted.localPaths;
4349
+ source = datasetSourceSchema.parse({ ...source, conversion: converted.conversion });
4350
+ }
4351
+ return fineTuneRunRequestSchema.parse({
4352
+ ...request,
4353
+ dataset_prebuilt: {
4354
+ ...localPaths,
4355
+ format: dataset.format
4356
+ },
4357
+ dataset_source: source
4358
+ });
4359
+ }
4360
+ async function prefetchHuggingFaceDataset(args) {
4361
+ const dataset = huggingFaceDataset(args.request);
4362
+ if (!dataset) {
4363
+ throw new Error(
4364
+ "This spec has no dataset_prebuilt.huggingface source. Local dataset files need no download; set dataset_prebuilt.huggingface.repo to train on a Hugging Face dataset."
4365
+ );
4366
+ }
4367
+ const modelCache = args.config.paths.modelCache ? resolve10(args.config.paths.modelCache) : void 0;
4368
+ const hubCache = huggingFaceHubCache(modelCache);
4369
+ if (args.localOnly) {
4370
+ const resolved2 = await resolveCachedHuggingFaceDataset({ dataset, modelCache });
4371
+ return {
4372
+ ok: true,
4373
+ status: "verified",
4374
+ local_files_only: true,
4375
+ hub_cache: hubCache,
4376
+ size_bytes: Object.values(resolved2.source.files).reduce((total, file) => total + (file?.size_bytes ?? 0), 0),
4377
+ ...resolved2.source
4378
+ };
4379
+ }
4380
+ const artifactDir = resolve10(
4381
+ args.config.artifactRoot,
4382
+ "dataset-prefetch",
4383
+ `${safeName(dataset.huggingface.repo)}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`
4384
+ );
4385
+ await mkdir7(artifactDir, { recursive: true });
4386
+ const inputPath = join8(artifactDir, "prefetch-input.json");
4387
+ const outputPath = join8(artifactDir, "prefetch-output.json");
4388
+ const logPath = join8(artifactDir, "prefetch.log");
4389
+ await writeJson(inputPath, {
4390
+ repo: dataset.huggingface.repo,
4391
+ ...dataset.huggingface.revision ? { revision: dataset.huggingface.revision } : {},
4392
+ files: datasetFiles(dataset),
4393
+ ...modelCache ? { model_cache: modelCache } : {}
4394
+ });
4395
+ const entrypoint = buildBundledPythonCommand("prefetch_dataset.py", ["--input", inputPath, "--output", outputPath]);
4396
+ await args.reporter?.onEvent?.({
4397
+ stage: "dataset_prefetch",
4398
+ status: "running",
4399
+ message: "Downloading Hugging Face dataset files.",
4400
+ details: { repo: dataset.huggingface.repo, revision: dataset.huggingface.revision ?? "main", hub_cache: hubCache, log_path: logPath }
4401
+ });
4402
+ const { exitCode, stderr } = await runLoggedProcess({
4403
+ command: entrypoint.command,
4404
+ commandArgs: entrypoint.commandArgs,
4405
+ env: withBundledPythonEnvironment(withHuggingFaceCacheEnvironment(minimalMachineLearningEnvironment(process.env), modelCache)),
4406
+ logPath,
4407
+ reporter: args.reporter ? { ...args.reporter, verbose: true } : void 0,
4408
+ stage: "dataset_prefetch"
4409
+ });
4410
+ if (exitCode !== 0) {
4411
+ const reason = stderr.trim().split("\n").filter(Boolean).at(-1);
4412
+ throw new Error(
4413
+ `Dataset prefetch exited with code ${exitCode}${reason ? `: ${reason}` : ""}. See ${logPath}. Rerunning resumes from the files already downloaded.`
4414
+ );
4415
+ }
4416
+ const output = JSON.parse(await readFile7(outputPath, "utf8"));
4417
+ const revision = baseModelRevisionSchema.parse(output.snapshot_revision);
4418
+ const resolved = await resolveCachedHuggingFaceDataset({
4419
+ dataset: { ...dataset, huggingface: { ...dataset.huggingface, revision } },
4420
+ modelCache
4421
+ });
4422
+ for (const [split, file] of Object.entries(resolved.source.files)) {
4423
+ if (output.files?.[split]?.sha256 !== file?.sha256) {
4424
+ throw new Error(`Dataset prefetch reported a different digest for ${split} than the cached file.`);
4425
+ }
4426
+ }
4427
+ const source = datasetSourceSchema.parse({
4428
+ ...resolved.source,
4429
+ requested_revision: dataset.huggingface.revision ?? null,
4430
+ pinned: Boolean(dataset.huggingface.revision)
4431
+ });
4432
+ const sizeBytes = Object.values(source.files).reduce((total, file) => total + (file?.size_bytes ?? 0), 0);
4433
+ await args.reporter?.onEvent?.({
4434
+ stage: "dataset_prefetch",
4435
+ status: "completed",
4436
+ message: "Dataset files are available in the local Hugging Face cache.",
4437
+ details: { repo: source.repo, revision: source.revision, pinned: source.pinned, size_bytes: sizeBytes }
4438
+ });
4439
+ return {
4440
+ ok: true,
4441
+ status: "completed",
4442
+ local_files_only: false,
4443
+ hub_cache: hubCache,
4444
+ size_bytes: sizeBytes,
4445
+ ...source,
4446
+ artifact_dir: artifactDir,
4447
+ log_uri: fileUri(logPath),
4448
+ command: entrypoint.displayCommand
4449
+ };
4450
+ }
4451
+ async function ensureDatasetCached(args) {
4452
+ if (!huggingFaceDataset(args.request)) return { request: args.request, prefetch: null };
4453
+ try {
4454
+ return { request: await resolveRequestDataset(args.request, args.config), prefetch: null };
4455
+ } catch (error) {
4456
+ if (!(error instanceof DatasetNotDownloadedError) || args.config.dryRun) throw error;
4457
+ }
4458
+ const prefetch = await prefetchHuggingFaceDataset(args);
4459
+ return { request: await resolveRequestDataset(args.request, args.config), prefetch };
4460
+ }
4461
+
3505
4462
  // src/local-runtime/general-regression.ts
3506
4463
  function evaluateGeneralRegressionGate(comparison, policy) {
3507
4464
  const failures = [];
@@ -3522,18 +4479,18 @@ function evaluateGeneralRegressionGate(comparison, policy) {
3522
4479
  }
3523
4480
 
3524
4481
  // src/local-runtime/orchestrator.ts
3525
- import { lstat as lstat5, mkdir as mkdir9, open as open3, readFile as readFile8, readdir as readdir5, rename as rename3, rm as rm4, stat as stat7 } from "fs/promises";
3526
- import { createReadStream as createReadStream3 } from "fs";
3527
- import { createHash as createHash7, randomUUID as randomUUID5 } from "crypto";
4482
+ import { lstat as lstat5, mkdir as mkdir11, open as open4, readFile as readFile10, readdir as readdir5, rename as rename4, rm as rm5, stat as stat9 } from "fs/promises";
4483
+ import { createReadStream as createReadStream5 } from "fs";
4484
+ import { createHash as createHash9, randomUUID as randomUUID6 } from "crypto";
3528
4485
  import { performance } from "perf_hooks";
3529
4486
  import { homedir as homedir4 } from "os";
3530
- import { basename as basename2, dirname as dirname9, join as join10, relative as relative4, resolve as resolve13 } from "path";
4487
+ import { basename as basename3, dirname as dirname9, join as join12, relative as relative5, resolve as resolve14 } from "path";
3531
4488
 
3532
4489
  // src/local-runtime/gpu-executor.ts
3533
- import { createHash as createHash5, randomUUID as randomUUID4 } from "crypto";
3534
- import { mkdtemp, mkdir as mkdir6, realpath as realpath3, rm as rm2, stat as stat5, writeFile as writeFile4 } from "fs/promises";
4490
+ import { createHash as createHash7, randomUUID as randomUUID5 } from "crypto";
4491
+ import { mkdtemp, mkdir as mkdir8, realpath as realpath4, rm as rm3, stat as stat7, writeFile as writeFile5 } from "fs/promises";
3535
4492
  import { tmpdir } from "os";
3536
- import { basename, dirname as dirname7, join as join7, resolve as resolve10 } from "path";
4493
+ import { basename as basename2, dirname as dirname7, join as join9, resolve as resolve11 } from "path";
3537
4494
  function shellQuote(value) {
3538
4495
  return `'${value.replaceAll("'", "'\\''")}'`;
3539
4496
  }
@@ -3598,23 +4555,23 @@ async function checkAwsGpu(gpu) {
3598
4555
  if (result.exitCode !== 0) throw new Error(`AWS GPU preflight failed: ${result.stderr}`);
3599
4556
  }
3600
4557
  function gpuBaseModelPath(config, model, revision) {
3601
- if (config.paths.baseModel) return resolve10(config.paths.baseModel);
4558
+ if (config.paths.baseModel) return resolve11(config.paths.baseModel);
3602
4559
  if (!revision || !/^[a-f0-9]{40}$/i.test(revision)) {
3603
4560
  throw new Error("AWS GPU execution needs an immutable base-model revision. Run tt models prefetch first or configure paths.baseModel.");
3604
4561
  }
3605
4562
  const env = withHuggingFaceCacheEnvironment(process.env, config.paths.modelCache);
3606
- return join7(env.HF_HUB_CACHE, `models--${model.replaceAll("/", "--")}`, "snapshots", revision);
4563
+ return join9(env.HF_HUB_CACHE, `models--${model.replaceAll("/", "--")}`, "snapshots", revision);
3607
4564
  }
3608
4565
  async function physicalTransferPath(path) {
3609
4566
  try {
3610
- return await realpath3(path);
4567
+ return await realpath4(path);
3611
4568
  } catch (error) {
3612
4569
  if (error.code !== "ENOENT" || dirname7(path) === path) throw error;
3613
- return join7(await physicalTransferPath(dirname7(path)), basename(path));
4570
+ return join9(await physicalTransferPath(dirname7(path)), basename2(path));
3614
4571
  }
3615
4572
  }
3616
4573
  async function assertSeparateTransferPaths(files) {
3617
- const paths = await Promise.all(files.map((file) => physicalTransferPath(resolve10(file.path))));
4574
+ const paths = await Promise.all(files.map((file) => physicalTransferPath(resolve11(file.path))));
3618
4575
  for (let i = 0; i < files.length; i += 1) {
3619
4576
  for (let j = 0; j < i; j += 1) {
3620
4577
  if (files[i].direction === "input" && files[j].direction === "input") continue;
@@ -3631,13 +4588,13 @@ async function runGpuProcess(args) {
3631
4588
  await assertSeparateTransferPaths(args.files);
3632
4589
  const host = await resolveGpuHost(args.gpu);
3633
4590
  const options = sshOptions(args.gpu);
3634
- const identity = args.logPath ? createHash5("sha256").update(resolve10(args.logPath)).digest("hex").slice(0, 32) : randomUUID4();
4591
+ const identity = args.logPath ? createHash7("sha256").update(resolve11(args.logPath)).digest("hex").slice(0, 32) : randomUUID5();
3635
4592
  const root = `/tmp/tt-gpu-${identity}`;
3636
- const scratch = await mkdtemp(join7(tmpdir(), "tt-gpu-"));
3637
- const runtime = join7(localRuntimePackageRoot(import.meta.url), "training", args.runtime);
3638
- const mappings = args.files.map((file, index) => ({ ...file, path: resolve10(file.path), remote: `${root}/files/${index}/${basename(file.path)}` }));
4593
+ const scratch = await mkdtemp(join9(tmpdir(), "tt-gpu-"));
4594
+ const runtime = join9(localRuntimePackageRoot(import.meta.url), "training", args.runtime);
4595
+ const mappings = args.files.map((file, index) => ({ ...file, path: resolve11(file.path), remote: `${root}/files/${index}/${basename2(file.path)}` }));
3639
4596
  const mapPath = (path) => {
3640
- const absolute = resolve10(path);
4597
+ const absolute = resolve11(path);
3641
4598
  const match = [...mappings].sort((a, b) => b.path.length - a.path.length).find((file) => absolute === file.path || file.directory && absolute.startsWith(`${file.path}/`));
3642
4599
  if (match) return match.remote + absolute.slice(match.path.length);
3643
4600
  if (absolute === runtime || absolute.startsWith(`${runtime}/`)) return `${root}/runtime${absolute.slice(runtime.length)}`;
@@ -3675,7 +4632,7 @@ async function runGpuProcess(args) {
3675
4632
  };
3676
4633
  const grace = Math.ceil((args.shutdownGraceMs ?? 5e3) / 1e3);
3677
4634
  const stop = `kill -TERM -- -"$pid" 2>/dev/null || true; remaining=${grace}; while kill -0 -- -"$pid" 2>/dev/null && [ "$remaining" -gt 0 ]; do sleep 1; remaining=$((remaining - 1)); done; kill -KILL -- -"$pid" 2>/dev/null || true`;
3678
- const owner = randomUUID4();
4635
+ const owner = randomUUID5();
3679
4636
  let reservationAttempted = false;
3680
4637
  let started = false;
3681
4638
  let completed = false;
@@ -3686,12 +4643,12 @@ async function runGpuProcess(args) {
3686
4643
  throw new Error(`Cannot reserve GPU staging directory ${host}:${root}. Another process or unrecovered artifacts may be present. ${error instanceof Error ? error.message : error}`);
3687
4644
  });
3688
4645
  for (const name of ["pyproject.toml", "uv.lock", "src"]) {
3689
- await sync(join7(runtime, name), `${root}/runtime/`, true);
4646
+ await sync(join9(runtime, name), `${root}/runtime/`, true);
3690
4647
  }
3691
4648
  for (const file of mappings) {
3692
4649
  await ssh(`mkdir -p ${shellQuote(file.directory ? file.remote : dirname7(file.remote))}`);
3693
4650
  if (file.direction !== "output") {
3694
- const exists3 = await stat5(file.path).catch((error) => {
4651
+ const exists3 = await stat7(file.path).catch((error) => {
3695
4652
  if (error.code === "ENOENT" && file.direction === "both") return void 0;
3696
4653
  throw error;
3697
4654
  });
@@ -3701,8 +4658,8 @@ async function runGpuProcess(args) {
3701
4658
  if (args.document) {
3702
4659
  const value = { ...args.document.value };
3703
4660
  for (const key of args.document.pathKeys) if (typeof value[key] === "string") value[key] = mapPath(value[key]);
3704
- const path = join7(scratch, "document.json");
3705
- await writeFile4(path, JSON.stringify(value), { mode: 384 });
4661
+ const path = join9(scratch, "document.json");
4662
+ await writeFile5(path, JSON.stringify(value), { mode: 384 });
3706
4663
  await sync(path, mapPath(args.document.path), true);
3707
4664
  }
3708
4665
  const env = {
@@ -3734,7 +4691,7 @@ async function runGpuProcess(args) {
3734
4691
  if (started) {
3735
4692
  await ssh(`bash -c ${shellQuote(`if test -f ${root}/pid; then pid=$(cat ${root}/pid); ${stop}; fi`)}`, false);
3736
4693
  for (const file of mappings.filter((file2) => file2.direction !== "input")) {
3737
- await mkdir6(file.directory ? file.path : dirname7(file.path), { recursive: true });
4694
+ await mkdir8(file.directory ? file.path : dirname7(file.path), { recursive: true });
3738
4695
  if (!completed && !file.directory) {
3739
4696
  const exists3 = await runLoggedProcess({ command: "ssh", commandArgs: [...options, host, `test -e ${shellQuote(file.remote)}`], stage: args.stage, timeoutMs: 3e4 });
3740
4697
  if (exists3.exitCode === 1) continue;
@@ -3747,15 +4704,15 @@ async function runGpuProcess(args) {
3747
4704
  } catch (error) {
3748
4705
  throw new Error(`GPU cleanup or artifact retrieval failed. Remote files remain at ${host}:${root}. ${error instanceof Error ? error.message : error}`);
3749
4706
  } finally {
3750
- await rm2(scratch, { recursive: true, force: true });
4707
+ await rm3(scratch, { recursive: true, force: true });
3751
4708
  }
3752
4709
  }
3753
4710
  }
3754
4711
 
3755
4712
  // src/local-runtime/evaluation.ts
3756
- import { createHash as createHash6 } from "crypto";
3757
- import { mkdir as mkdir7, readFile as readFile6, rm as rm3, stat as stat6, writeFile as writeFile5 } from "fs/promises";
3758
- import { dirname as dirname8, join as join8, resolve as resolve11 } from "path";
4713
+ import { createHash as createHash8 } from "crypto";
4714
+ import { mkdir as mkdir9, readFile as readFile8, rm as rm4, stat as stat8, writeFile as writeFile6 } from "fs/promises";
4715
+ import { dirname as dirname8, join as join10, resolve as resolve12 } from "path";
3759
4716
  function normalize(value) {
3760
4717
  return value.trim().replace(/\s+/g, " ").toLowerCase();
3761
4718
  }
@@ -3849,11 +4806,11 @@ function extractJsonObject(value) {
3849
4806
  }
3850
4807
  return null;
3851
4808
  }
3852
- function canonicalJson3(value) {
3853
- if (Array.isArray(value)) return value.map(canonicalJson3);
4809
+ function canonicalJson4(value) {
4810
+ if (Array.isArray(value)) return value.map(canonicalJson4);
3854
4811
  if (value && typeof value === "object") {
3855
4812
  return Object.fromEntries(
3856
- Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonicalJson3(item)])
4813
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonicalJson4(item)])
3857
4814
  );
3858
4815
  }
3859
4816
  return value;
@@ -3879,7 +4836,7 @@ function scoreJsonFields(expected, actual, configuredFields) {
3879
4836
  const schemaMatch = Boolean(actualJson) && expectedKeys.length === actualKeys.length && expectedKeys.every((key, index) => key === actualKeys[index]);
3880
4837
  const fieldResults = {};
3881
4838
  for (const field of fields) {
3882
- fieldResults[field] = Object.prototype.hasOwnProperty.call(expectedJson, field) && Boolean(actualJson) && JSON.stringify(canonicalJson3(expectedJson[field])) === JSON.stringify(canonicalJson3(actualJson?.[field]));
4839
+ fieldResults[field] = Object.prototype.hasOwnProperty.call(expectedJson, field) && Boolean(actualJson) && JSON.stringify(canonicalJson4(expectedJson[field])) === JSON.stringify(canonicalJson4(actualJson?.[field]));
3883
4840
  }
3884
4841
  const correct = Object.values(fieldResults).filter(Boolean).length;
3885
4842
  const score = fields.length > 0 ? correct / fields.length : 0;
@@ -3975,8 +4932,8 @@ async function runTransformersInference(args) {
3975
4932
  const inputPath = `${args.outputPath}.inference-input.json`;
3976
4933
  const outputPath = `${args.outputPath}.inference-output.json`;
3977
4934
  const logPath = `${args.outputPath}.inference.log`;
3978
- await mkdir7(dirname8(inputPath), { recursive: true });
3979
- await writeFile5(inputPath, `${JSON.stringify({
4935
+ await mkdir9(dirname8(inputPath), { recursive: true });
4936
+ await writeFile6(inputPath, `${JSON.stringify({
3980
4937
  protocol_version: INFERENCE_PROTOCOL_VERSION,
3981
4938
  kind: args.kind,
3982
4939
  model_id: args.modelId,
@@ -3990,7 +4947,7 @@ async function runTransformersInference(args) {
3990
4947
  id: String(index),
3991
4948
  input: example.input
3992
4949
  })),
3993
- model_cache: args.config.paths.modelCache ? resolve11(args.config.paths.modelCache) : void 0,
4950
+ model_cache: args.config.paths.modelCache ? resolve12(args.config.paths.modelCache) : void 0,
3994
4951
  trust_remote_code: false,
3995
4952
  device: args.config.evaluation.inference.device,
3996
4953
  generation: {
@@ -4000,7 +4957,7 @@ async function runTransformersInference(args) {
4000
4957
  }
4001
4958
  }, null, 2)}
4002
4959
  `, "utf8");
4003
- await rm3(outputPath, { force: true });
4960
+ await rm4(outputPath, { force: true });
4004
4961
  const entrypoint = buildBundledPythonCommand(
4005
4962
  "evaluate.py",
4006
4963
  ["--input", inputPath, "--output", outputPath]
@@ -4018,7 +4975,7 @@ async function runTransformersInference(args) {
4018
4975
  });
4019
4976
  const gpu = args.config.evaluation.inference.device === "cuda" ? args.config.gpu : void 0;
4020
4977
  const execute = gpu ? async (processArgs) => {
4021
- const document = JSON.parse(await readFile6(inputPath, "utf8"));
4978
+ const document = JSON.parse(await readFile8(inputPath, "utf8"));
4022
4979
  document.model_source = gpuBaseModelPath(args.config, args.baseModelId, args.baseModelRevision);
4023
4980
  delete document.model_cache;
4024
4981
  const files = [
@@ -4028,7 +4985,7 @@ async function runTransformersInference(args) {
4028
4985
  ...document.adapter_path ? [{
4029
4986
  path: document.adapter_path,
4030
4987
  direction: "input",
4031
- directory: (await stat6(document.adapter_path)).isDirectory()
4988
+ directory: (await stat8(document.adapter_path)).isDirectory()
4032
4989
  }] : []
4033
4990
  ];
4034
4991
  return runGpuProcess({
@@ -4062,7 +5019,7 @@ async function runTransformersInference(args) {
4062
5019
  }
4063
5020
  let output;
4064
5021
  try {
4065
- output = JSON.parse(await readFile6(outputPath, "utf8"));
5022
+ output = JSON.parse(await readFile8(outputPath, "utf8"));
4066
5023
  } catch (error) {
4067
5024
  throw new Error(
4068
5025
  `Transformers inference did not write valid JSON: ${error instanceof Error ? error.message : String(error)}`
@@ -4079,7 +5036,7 @@ function assertEvaluationScoringReady(config) {
4079
5036
  }
4080
5037
  }
4081
5038
  function baselineCacheKey(args) {
4082
- return createHash6("sha256").update(JSON.stringify({
5039
+ return createHash8("sha256").update(JSON.stringify({
4083
5040
  v: 6,
4084
5041
  inference_protocol_version: INFERENCE_PROTOCOL_VERSION,
4085
5042
  package_version: args.packageVersion,
@@ -4099,15 +5056,15 @@ function baselineInputsAreStable(args) {
4099
5056
  return args.config.paths.baseModel ? Boolean(args.sourceFingerprint) : Boolean(args.baseModelRevision);
4100
5057
  }
4101
5058
  function baselineStoreRoot(config) {
4102
- return config.storeRoot ? resolve11(config.storeRoot) : defaultLocalHome();
5059
+ return config.storeRoot ? resolve12(config.storeRoot) : defaultLocalHome();
4103
5060
  }
4104
5061
  function baselineCachePath(config, key) {
4105
- return join8(baselineStoreRoot(config), "cache", "baseline-evals", `${key}.json`);
5062
+ return join10(baselineStoreRoot(config), "cache", "baseline-evals", `${key}.json`);
4106
5063
  }
4107
5064
  async function readBaselineCache(config, path) {
4108
5065
  await hardenExistingLocalStore(baselineStoreRoot(config));
4109
5066
  try {
4110
- return evalReportSchema.parse(JSON.parse(await readFile6(path, "utf8")));
5067
+ return evalReportSchema.parse(JSON.parse(await readFile8(path, "utf8")));
4111
5068
  } catch {
4112
5069
  return null;
4113
5070
  }
@@ -4116,7 +5073,7 @@ var cachedPackageVersion = null;
4116
5073
  async function packageVersion() {
4117
5074
  if (cachedPackageVersion !== null) return cachedPackageVersion;
4118
5075
  try {
4119
- const raw = await readFile6(resolve11(localRuntimePackageRoot(import.meta.url), "package.json"), "utf8");
5076
+ const raw = await readFile8(resolve12(localRuntimePackageRoot(import.meta.url), "package.json"), "utf8");
4120
5077
  cachedPackageVersion = String(
4121
5078
  JSON.parse(raw).version ?? "unknown"
4122
5079
  );
@@ -4185,7 +5142,7 @@ async function evaluateExamples(args) {
4185
5142
  baseModelId,
4186
5143
  args.baseModelRevision
4187
5144
  );
4188
- const localModelSource = args.modelSource ? resolve11(args.modelSource) !== resolve11(baseModelId) : false;
5145
+ const localModelSource = args.modelSource ? resolve12(args.modelSource) !== resolve12(baseModelId) : false;
4189
5146
  const baseModelRevision = localModelSource ? defaultBaseModelRevision(baseModelId) : requestedBaseModelRevision;
4190
5147
  const maxExamples = args.config.evaluation.maxExamples ?? args.maxExamples ?? args.examples.length;
4191
5148
  const truncated = args.examples.length > maxExamples;
@@ -4301,8 +5258,8 @@ function compareEvalReports(baseline, candidate) {
4301
5258
  }
4302
5259
 
4303
5260
  // src/local-runtime/process-training.ts
4304
- import { access, mkdir as mkdir8, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
4305
- import { join as join9, resolve as resolve12 } from "path";
5261
+ import { access, mkdir as mkdir10, readFile as readFile9, writeFile as writeFile7 } from "fs/promises";
5262
+ import { join as join11, resolve as resolve13 } from "path";
4306
5263
  function buildTrainingHyperparameters(request, options = {}) {
4307
5264
  const model = resolveTrainingModel(request.spec_snapshot.base_model);
4308
5265
  const hyper = request.hyperparameters;
@@ -4393,15 +5350,15 @@ function createTrainingProgressForwarder(reporter) {
4393
5350
  async function launchProcessTraining(args) {
4394
5351
  const { request, artifacts, config } = args;
4395
5352
  const jobName = `tt-local-${request.run_id}`;
4396
- await mkdir8(artifacts.trainingDir, { recursive: true });
5353
+ await mkdir10(artifacts.trainingDir, { recursive: true });
4397
5354
  await copyDatasetToTrainingChannel(artifacts);
4398
5355
  const hyperparameters = buildTrainingHyperparameters(request, {
4399
5356
  baseModelRevision: args.baseModelRevision
4400
5357
  });
4401
- await writeJson(join9(artifacts.trainingConfigDir, "hyperparameters.json"), hyperparameters);
5358
+ await writeJson(join11(artifacts.trainingConfigDir, "hyperparameters.json"), hyperparameters);
4402
5359
  const entrypoint = buildBundledPythonCommand("train.py");
4403
5360
  if (config.dryRun) {
4404
- await writeFile6(
5361
+ await writeFile7(
4405
5362
  artifacts.trainingLog,
4406
5363
  "Dry run enabled. Training process was not launched.\n",
4407
5364
  "utf8"
@@ -4423,11 +5380,11 @@ async function launchProcessTraining(args) {
4423
5380
  withOfflineHuggingFaceCacheEnvironment({
4424
5381
  ...inheritedEnv,
4425
5382
  BACKEND: "local",
4426
- SM_CHANNEL_TRAINING: resolve12(artifacts.trainingInputDir),
4427
- SM_CHANNEL_BASE_MODEL: config.paths.baseModel ? resolve12(config.paths.baseModel) : void 0,
4428
- SM_MODEL_DIR: resolve12(artifacts.trainingModelDir),
4429
- SM_OUTPUT_DIR: resolve12(artifacts.trainingOutputDir),
4430
- TT_HYPERPARAMETERS_PATH: resolve12(artifacts.trainingConfigDir, "hyperparameters.json")
5383
+ SM_CHANNEL_TRAINING: resolve13(artifacts.trainingInputDir),
5384
+ SM_CHANNEL_BASE_MODEL: config.paths.baseModel ? resolve13(config.paths.baseModel) : void 0,
5385
+ SM_MODEL_DIR: resolve13(artifacts.trainingModelDir),
5386
+ SM_OUTPUT_DIR: resolve13(artifacts.trainingOutputDir),
5387
+ TT_HYPERPARAMETERS_PATH: resolve13(artifacts.trainingConfigDir, "hyperparameters.json")
4431
5388
  }, config.paths.modelCache)
4432
5389
  );
4433
5390
  for (const [key, value] of Object.entries(childEnv)) {
@@ -4435,10 +5392,10 @@ async function launchProcessTraining(args) {
4435
5392
  }
4436
5393
  const remoteBaseModel = config.gpu ? gpuBaseModelPath(config, request.spec_snapshot.base_model, args.baseModelRevision) : void 0;
4437
5394
  if (remoteBaseModel) childEnv.SM_CHANNEL_BASE_MODEL = remoteBaseModel;
4438
- if (config.paths.modelCache) await mkdir8(resolve12(config.paths.modelCache), { recursive: true });
5395
+ if (config.paths.modelCache) await mkdir10(resolve13(config.paths.modelCache), { recursive: true });
4439
5396
  if (config.paths.baseModel) {
4440
5397
  await verifyLocalBaseModel(
4441
- resolve12(config.paths.baseModel),
5398
+ resolve13(config.paths.baseModel),
4442
5399
  request.spec_snapshot.base_model,
4443
5400
  config.paths.modelCache
4444
5401
  );
@@ -4477,12 +5434,12 @@ async function launchProcessTraining(args) {
4477
5434
  onLine: (line) => forwardTrainingProgress(line),
4478
5435
  shouldCancel: args.shouldCancel
4479
5436
  });
4480
- const metricsPath = join9(artifacts.trainingModelDir, "training-metrics.json");
4481
- const metrics = await pathExists(metricsPath) ? JSON.parse(await readFile7(metricsPath, "utf8")) : null;
5437
+ const metricsPath = join11(artifacts.trainingModelDir, "training-metrics.json");
5438
+ const metrics = await pathExists(metricsPath) ? JSON.parse(await readFile9(metricsPath, "utf8")) : null;
4482
5439
  if (exitCode !== 0) {
4483
5440
  throw new Error(`Training process exited with code ${exitCode}. See ${artifacts.trainingLog}.`);
4484
5441
  }
4485
- const modelTarPath = join9(artifacts.trainingOutputDir, "model.tar.gz");
5442
+ const modelTarPath = join11(artifacts.trainingOutputDir, "model.tar.gz");
4486
5443
  const modelUri = await pathExists(modelTarPath) ? fileUri(modelTarPath) : fileUri(artifacts.trainingModelDir);
4487
5444
  await args.reporter?.onEvent?.({
4488
5445
  stage: "training",
@@ -4577,7 +5534,7 @@ function validateLocalPipeline(pipeline) {
4577
5534
  return pipeline;
4578
5535
  }
4579
5536
  async function loadJsonFile(path) {
4580
- return JSON.parse(await readFile8(path, "utf8"));
5537
+ return JSON.parse(await readFile10(path, "utf8"));
4581
5538
  }
4582
5539
  async function loadRunRequest(path) {
4583
5540
  return fineTuneRunRequestSchema.parse(await loadJsonFile(path));
@@ -4585,13 +5542,13 @@ async function loadRunRequest(path) {
4585
5542
  function parseLocalRunnerConfig(document, path) {
4586
5543
  const config = localRunnerConfigSchema.parse(document);
4587
5544
  if (!path) return config;
4588
- const configPath = resolve13(path);
5545
+ const configPath = resolve14(path);
4589
5546
  const base = dirname9(configPath);
4590
5547
  const configPathValue = (value) => {
4591
5548
  if (!value) return void 0;
4592
5549
  if (value === "~") return homedir4();
4593
- if (value.startsWith("~/")) return resolve13(homedir4(), value.slice(2));
4594
- return resolve13(base, value);
5550
+ if (value.startsWith("~/")) return resolve14(homedir4(), value.slice(2));
5551
+ return resolve14(base, value);
4595
5552
  };
4596
5553
  return {
4597
5554
  ...config,
@@ -4613,7 +5570,7 @@ function parseLocalRunnerConfig(document, path) {
4613
5570
  }
4614
5571
  async function loadLocalRunnerConfig(path) {
4615
5572
  if (!path) return parseLocalRunnerConfig({});
4616
- const configPath = resolve13(path);
5573
+ const configPath = resolve14(path);
4617
5574
  return parseLocalRunnerConfig(
4618
5575
  await loadJsonFile(configPath),
4619
5576
  configPath
@@ -4628,7 +5585,7 @@ function stripFileUri(path) {
4628
5585
  }
4629
5586
  async function pathExists2(path) {
4630
5587
  try {
4631
- await stat7(path);
5588
+ await stat9(path);
4632
5589
  return true;
4633
5590
  } catch {
4634
5591
  return false;
@@ -4672,7 +5629,7 @@ function addDryRunPlaceholders(request, dryRun) {
4672
5629
  };
4673
5630
  }
4674
5631
  async function validateDatasetInputs(request, config) {
4675
- const inputIdentity = (value) => value.normalize("NFKC").trim().replace(/\s+/g, " ").toLocaleLowerCase("en");
5632
+ const inputIdentity = datasetInputIdentity;
4676
5633
  const evalSampleSeed = config.evaluation.sampleSeed ?? deriveSampleSeed(request.behavior_spec_id);
4677
5634
  if (!request.dataset_prebuilt) {
4678
5635
  const inlineExamples = examplesFromSpec(request.spec_snapshot);
@@ -4762,7 +5719,10 @@ async function validateDatasetInputs(request, config) {
4762
5719
  }
4763
5720
  async function validateLocalFineTuneInput(input) {
4764
5721
  const config = localRunnerConfigSchema.parse(input.config);
4765
- const request = fineTuneRunRequestSchema.parse(addDryRunPlaceholders(input.request, config.dryRun));
5722
+ const request = await resolveRequestDataset(
5723
+ fineTuneRunRequestSchema.parse(addDryRunPlaceholders(input.request, config.dryRun)),
5724
+ config
5725
+ );
4766
5726
  await validateDatasetInputs(request, config);
4767
5727
  if (config.evaluation.generalRegression) {
4768
5728
  await evaluationSuiteFromChatJsonl(
@@ -4779,14 +5739,14 @@ function artifactPrefix(request) {
4779
5739
  runId: request.run_id
4780
5740
  });
4781
5741
  }
4782
- function canonicalJson4(value) {
5742
+ function canonicalJson5(value) {
4783
5743
  if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
4784
- if (Array.isArray(value)) return `[${value.map((entry) => canonicalJson4(entry)).join(",")}]`;
5744
+ if (Array.isArray(value)) return `[${value.map((entry) => canonicalJson5(entry)).join(",")}]`;
4785
5745
  const object = value;
4786
- return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson4(object[key])}`).join(",")}}`;
5746
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson5(object[key])}`).join(",")}}`;
4787
5747
  }
4788
5748
  function hashJson(value) {
4789
- return createHash7("sha256").update(canonicalJson4(value)).digest("hex");
5749
+ return createHash9("sha256").update(canonicalJson5(value)).digest("hex");
4790
5750
  }
4791
5751
  var packageRoot2 = localRuntimePackageRoot(import.meta.url);
4792
5752
  async function hashFileIfPresent(path) {
@@ -4798,21 +5758,21 @@ async function hashFileIfPresent(path) {
4798
5758
  }
4799
5759
  async function packageVersion2() {
4800
5760
  try {
4801
- const parsed = JSON.parse(await readFile8(resolve13(packageRoot2, "package.json"), "utf8"));
5761
+ const parsed = JSON.parse(await readFile10(resolve14(packageRoot2, "package.json"), "utf8"));
4802
5762
  return typeof parsed.version === "string" ? parsed.version : "unknown";
4803
5763
  } catch {
4804
5764
  return "unknown";
4805
5765
  }
4806
5766
  }
4807
5767
  async function runtimeFingerprint() {
4808
- const bundledProject2 = resolve13(packageRoot2, "training/adapter");
5768
+ const bundledProject2 = resolve14(packageRoot2, "training/adapter");
4809
5769
  return hashJson({
4810
5770
  tt_local_version: await packageVersion2(),
4811
5771
  node_version: process.version,
4812
5772
  platform: process.platform,
4813
5773
  architecture: process.arch,
4814
- runner_pyproject: await hashFileIfPresent(resolve13(bundledProject2, "pyproject.toml")),
4815
- runner_uv_lock: await hashFileIfPresent(resolve13(bundledProject2, "uv.lock"))
5774
+ runner_pyproject: await hashFileIfPresent(resolve14(bundledProject2, "pyproject.toml")),
5775
+ runner_uv_lock: await hashFileIfPresent(resolve14(bundledProject2, "uv.lock"))
4816
5776
  });
4817
5777
  }
4818
5778
  function preparedSourceFingerprint(args) {
@@ -4826,8 +5786,8 @@ function preparedSourceFingerprint(args) {
4826
5786
  });
4827
5787
  }
4828
5788
  async function hashFile(path) {
4829
- const hash = createHash7("sha256");
4830
- for await (const chunk of createReadStream3(path)) hash.update(chunk);
5789
+ const hash = createHash9("sha256");
5790
+ for await (const chunk of createReadStream5(path)) hash.update(chunk);
4831
5791
  return hash.digest("hex");
4832
5792
  }
4833
5793
  async function datasetFingerprints(request) {
@@ -4859,9 +5819,9 @@ async function resolveBaseModelRevision(request, config) {
4859
5819
  }
4860
5820
  const repository = `models--${request.spec_snapshot.base_model.replaceAll("/", "--")}`;
4861
5821
  const cacheEnvironment = withHuggingFaceCacheEnvironment(process.env, config.paths.modelCache);
4862
- const refPath = resolve13(cacheEnvironment.HF_HUB_CACHE, repository, "refs", "main");
5822
+ const refPath = resolve14(cacheEnvironment.HF_HUB_CACHE, repository, "refs", "main");
4863
5823
  try {
4864
- const revision = (await readFile8(refPath, "utf8")).trim();
5824
+ const revision = (await readFile10(refPath, "utf8")).trim();
4865
5825
  if (revision) return revision;
4866
5826
  } catch {
4867
5827
  }
@@ -4880,18 +5840,18 @@ async function fingerprintLocalBaseModel(uri, expectedModelId, modelCache) {
4880
5840
  const files = [];
4881
5841
  const visit = async (directory) => {
4882
5842
  for (const entry of await readdir5(directory, { withFileTypes: true })) {
4883
- const child = resolve13(directory, entry.name);
5843
+ const child = resolve14(directory, entry.name);
4884
5844
  if (entry.isDirectory()) {
4885
5845
  await visit(child);
4886
5846
  continue;
4887
5847
  }
4888
- const childMetadata = await stat7(child);
5848
+ const childMetadata = await stat9(child);
4889
5849
  if (entry.isSymbolicLink() && !childMetadata.isFile()) {
4890
5850
  throw new Error(`Local base model contains a non-file symbolic link: ${child}`);
4891
5851
  }
4892
5852
  if (!childMetadata.isFile()) continue;
4893
5853
  files.push({
4894
- path: relative4(root, child).split("\\").join("/"),
5854
+ path: relative5(root, child).split("\\").join("/"),
4895
5855
  size_bytes: childMetadata.size,
4896
5856
  sha256: await hashFile(child)
4897
5857
  });
@@ -4920,26 +5880,26 @@ async function readStageMetadata(path) {
4920
5880
  }
4921
5881
  async function removePrefixedArtifacts(path) {
4922
5882
  const directory = dirname9(path);
4923
- const prefix = `${basename2(path)}.`;
5883
+ const prefix = `${basename3(path)}.`;
4924
5884
  const names = await readdir5(directory).catch(() => []);
4925
5885
  await Promise.all([
4926
- rm4(path, { force: true }),
4927
- ...names.filter((name) => name.startsWith(prefix)).map((name) => rm4(resolve13(directory, name), { recursive: true, force: true }))
5886
+ rm5(path, { force: true }),
5887
+ ...names.filter((name) => name.startsWith(prefix)).map((name) => rm5(resolve14(directory, name), { recursive: true, force: true }))
4928
5888
  ]);
4929
5889
  }
4930
5890
  async function cleanupStageArtifacts(artifacts, stage) {
4931
- const removeReport = async () => rm4(artifacts.runReportJson, { force: true });
5891
+ const removeReport = async () => rm5(artifacts.runReportJson, { force: true });
4932
5892
  if (stage === "prepare") {
4933
5893
  await Promise.all([
4934
5894
  removePrefixedArtifacts(artifacts.baselineEvalJson),
4935
5895
  removePrefixedArtifacts(artifacts.candidateEvalJson),
4936
5896
  removePrefixedArtifacts(artifacts.generalBaselineEvalJson),
4937
5897
  removePrefixedArtifacts(artifacts.generalCandidateEvalJson),
4938
- rm4(artifacts.trainingDir, { recursive: true, force: true }),
5898
+ rm5(artifacts.trainingDir, { recursive: true, force: true }),
4939
5899
  removePrefixedArtifacts(artifacts.trainingReportJson),
4940
- rm4(resolve13(artifacts.runDir, "model.tar.gz"), { force: true }),
5900
+ rm5(resolve14(artifacts.runDir, "model.tar.gz"), { force: true }),
4941
5901
  removeReport(),
4942
- rm4(artifacts.artifactManifestJson, { force: true })
5902
+ rm5(artifacts.artifactManifestJson, { force: true })
4943
5903
  ]);
4944
5904
  return;
4945
5905
  }
@@ -4953,9 +5913,9 @@ async function cleanupStageArtifacts(artifacts, stage) {
4953
5913
  }
4954
5914
  if (stage === "train") {
4955
5915
  await Promise.all([
4956
- rm4(artifacts.trainingDir, { recursive: true, force: true }),
5916
+ rm5(artifacts.trainingDir, { recursive: true, force: true }),
4957
5917
  removePrefixedArtifacts(artifacts.trainingReportJson),
4958
- rm4(resolve13(artifacts.runDir, "model.tar.gz"), { force: true }),
5918
+ rm5(resolve14(artifacts.runDir, "model.tar.gz"), { force: true }),
4959
5919
  removePrefixedArtifacts(artifacts.candidateEvalJson),
4960
5920
  removePrefixedArtifacts(artifacts.generalCandidateEvalJson),
4961
5921
  removeReport()
@@ -4974,7 +5934,7 @@ async function cleanupStageArtifacts(artifacts, stage) {
4974
5934
  await removeReport();
4975
5935
  }
4976
5936
  function manifestRelativePath(artifacts, path) {
4977
- return relative4(artifacts.runDir, path).split("\\").join("/");
5937
+ return relative5(artifacts.runDir, path).split("\\").join("/");
4978
5938
  }
4979
5939
  async function verifyReusableArtifacts(artifacts, paths, options = {}) {
4980
5940
  if (!await pathExists2(artifacts.artifactManifestJson)) return false;
@@ -4991,11 +5951,11 @@ async function throwIfCancelled(store, request) {
4991
5951
  }
4992
5952
  }
4993
5953
  async function acquireWorkflowLock(lockPath, description) {
4994
- await mkdir9(dirname9(lockPath), { recursive: true });
4995
- const token = randomUUID5();
5954
+ await mkdir11(dirname9(lockPath), { recursive: true });
5955
+ const token = randomUUID6();
4996
5956
  for (let attempt = 0; attempt < 3; attempt += 1) {
4997
5957
  try {
4998
- const handle = await open3(lockPath, "wx", 384);
5958
+ const handle = await open4(lockPath, "wx", 384);
4999
5959
  try {
5000
5960
  await handle.writeFile(`${JSON.stringify({ token, pid: process.pid, created_at: (/* @__PURE__ */ new Date()).toISOString() })}
5001
5961
  `);
@@ -5004,17 +5964,17 @@ async function acquireWorkflowLock(lockPath, description) {
5004
5964
  }
5005
5965
  return async () => {
5006
5966
  try {
5007
- const owner = JSON.parse(await readFile8(lockPath, "utf8"));
5008
- if (owner.token === token) await rm4(lockPath, { force: true });
5967
+ const owner = JSON.parse(await readFile10(lockPath, "utf8"));
5968
+ if (owner.token === token) await rm5(lockPath, { force: true });
5009
5969
  } catch {
5010
5970
  }
5011
5971
  };
5012
5972
  } catch (error) {
5013
5973
  if (error.code !== "EEXIST") throw error;
5014
- const metadata = await stat7(lockPath).catch(() => null);
5974
+ const metadata = await stat9(lockPath).catch(() => null);
5015
5975
  let owner = null;
5016
5976
  try {
5017
- owner = JSON.parse(await readFile8(lockPath, "utf8"));
5977
+ owner = JSON.parse(await readFile10(lockPath, "utf8"));
5018
5978
  } catch {
5019
5979
  owner = null;
5020
5980
  }
@@ -5033,10 +5993,10 @@ async function acquireWorkflowLock(lockPath, description) {
5033
5993
  `${description} already has an active local workflow (lock: ${lockPath}).`
5034
5994
  );
5035
5995
  }
5036
- const stalePath = `${lockPath}.stale.${randomUUID5()}`;
5996
+ const stalePath = `${lockPath}.stale.${randomUUID6()}`;
5037
5997
  try {
5038
- await rename3(lockPath, stalePath);
5039
- await rm4(stalePath, { force: true });
5998
+ await rename4(lockPath, stalePath);
5999
+ await rm5(stalePath, { force: true });
5040
6000
  } catch (renameError) {
5041
6001
  if (renameError.code !== "ENOENT") throw renameError;
5042
6002
  }
@@ -5046,11 +6006,11 @@ async function acquireWorkflowLock(lockPath, description) {
5046
6006
  }
5047
6007
  async function acquireRunLock(store, runId) {
5048
6008
  await store.ensure();
5049
- return acquireWorkflowLock(resolve13(store.paths.runsDir, runId, "workflow.lock"), `Run ${runId}`);
6009
+ return acquireWorkflowLock(resolve14(store.paths.runsDir, runId, "workflow.lock"), `Run ${runId}`);
5050
6010
  }
5051
6011
  async function acquireArtifactLock(artifacts) {
5052
6012
  return acquireWorkflowLock(
5053
- join10(artifacts.runDir, ARTIFACT_WORKFLOW_LOCK_FILE),
6013
+ join12(artifacts.runDir, ARTIFACT_WORKFLOW_LOCK_FILE),
5054
6014
  `Artifact directory ${artifacts.runDir}`
5055
6015
  );
5056
6016
  }
@@ -5097,13 +6057,13 @@ function stageFingerprintPath(prepared, stage) {
5097
6057
  return `${stageOutputPath(prepared, stage)}.stage.json`;
5098
6058
  }
5099
6059
  async function runnerFileFingerprints() {
5100
- const project = resolve13(packageRoot2, "training/adapter");
5101
- const bundledSource = resolve13(packageRoot2, "training/adapter/src");
5102
- const pythonSources = (await readdir5(bundledSource, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".py")).map((entry) => resolve13(bundledSource, entry.name));
6060
+ const project = resolve14(packageRoot2, "training/adapter");
6061
+ const bundledSource = resolve14(packageRoot2, "training/adapter/src");
6062
+ const pythonSources = (await readdir5(bundledSource, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".py")).map((entry) => resolve14(bundledSource, entry.name));
5103
6063
  const candidates = [.../* @__PURE__ */ new Set([
5104
6064
  ...pythonSources,
5105
- resolve13(project, "pyproject.toml"),
5106
- resolve13(project, "uv.lock")
6065
+ resolve14(project, "pyproject.toml"),
6066
+ resolve14(project, "uv.lock")
5107
6067
  ])];
5108
6068
  const fingerprints = {};
5109
6069
  for (const path of candidates.sort()) fingerprints[path] = await hashFileIfPresent(path);
@@ -5217,7 +6177,7 @@ async function ensureRunRecord(args) {
5217
6177
  `Run ${args.request.run_id} cannot be reused with different user, behavior spec, or run number identity.`
5218
6178
  );
5219
6179
  }
5220
- if (resolve13(existing.artifact_dir) !== resolve13(args.artifacts.runDir)) {
6180
+ if (resolve14(existing.artifact_dir) !== resolve14(args.artifacts.runDir)) {
5221
6181
  throw new Error(
5222
6182
  `Run ${args.request.run_id} is already bound to artifact directory ${existing.artifact_dir}; it cannot be resumed with ${args.artifacts.runDir}.`
5223
6183
  );
@@ -5295,7 +6255,7 @@ async function computePreparedRun(args) {
5295
6255
  base_model_for_evaluation: baseModelForEvaluation,
5296
6256
  base_model_revision: baseModelRevision ?? null,
5297
6257
  base_model_fingerprint: baseModelFingerprint ?? null,
5298
- system_prompt_sha256: createHash7("sha256").update(system).digest("hex"),
6258
+ system_prompt_sha256: createHash9("sha256").update(system).digest("hex"),
5299
6259
  prepared_at: (/* @__PURE__ */ new Date()).toISOString()
5300
6260
  };
5301
6261
  if (args.writeArtifacts) await writeJsonAtomic(artifacts.stageMetadataJson, metadata);
@@ -5324,7 +6284,7 @@ async function prepareStage(args) {
5324
6284
  const preparedExists = await pathExists2(args.artifacts.stageMetadataJson) && await pathExists2(args.artifacts.trainingJsonl);
5325
6285
  const prepared = await computePreparedRun({ ...args, writeArtifacts: false });
5326
6286
  await args.store.syncRunRequest(args.request, args.artifacts.runDir);
5327
- await writeJsonAtomic(resolve13(args.artifacts.runDir, "request.json"), args.request);
6287
+ await writeJsonAtomic(resolve14(args.artifacts.runDir, "request.json"), args.request);
5328
6288
  const existingMetadata = preparedExists ? await readStageMetadata(args.artifacts.stageMetadataJson) : null;
5329
6289
  let canReuse = preparedExists && existingMetadata?.source_fingerprint === prepared.metadata.source_fingerprint;
5330
6290
  if (canReuse) {
@@ -5658,6 +6618,56 @@ async function runCandidateStage(args) {
5658
6618
  });
5659
6619
  return report;
5660
6620
  }
6621
+ async function buildRunProvenance(args) {
6622
+ const { metadata, request } = args.prepared;
6623
+ const gpu = args.config.gpu;
6624
+ return runProvenanceSchema.parse({
6625
+ schema_version: 1,
6626
+ recorded_at: args.recordedAt,
6627
+ software: {
6628
+ tt_version: await packageVersion2(),
6629
+ node_version: process.version,
6630
+ platform: process.platform,
6631
+ arch: process.arch,
6632
+ runtime_fingerprint: metadata.runtime_fingerprint,
6633
+ python_lock_sha256: await hashFileIfPresent(resolve14(packageRoot2, "training/adapter/uv.lock"))
6634
+ },
6635
+ execution: {
6636
+ target: gpu ? "aws" : "local",
6637
+ training_backend: args.training.provider,
6638
+ ...gpu ? { instance_id: gpu.instanceId, ...gpu.region ? { region: gpu.region } : {} } : {}
6639
+ },
6640
+ spec: {
6641
+ behavior_spec_id: request.behavior_spec_id,
6642
+ run_number: request.run_number,
6643
+ snapshot_sha256: hashJson(request.spec_snapshot),
6644
+ system_prompt_sha256: metadata.system_prompt_sha256,
6645
+ ...args.specFile ? { file_path: args.specFile.path, file_sha256: args.specFile.sha256 } : {}
6646
+ },
6647
+ base_model: {
6648
+ id: request.spec_snapshot.base_model,
6649
+ revision: metadata.base_model_revision,
6650
+ fingerprint: metadata.base_model_fingerprint
6651
+ },
6652
+ data: {
6653
+ prebuilt: metadata.dataset_prebuilt,
6654
+ format: metadata.dataset_format,
6655
+ file_sha256: metadata.dataset_fingerprints,
6656
+ compiled_training_sha256: await hashFileIfPresent(args.prepared.artifacts.trainingJsonl),
6657
+ training_examples: metadata.training_example_count,
6658
+ eval_split: metadata.eval_split,
6659
+ eval_sample_seed: metadata.eval_sample_seed,
6660
+ eval_examples_total: metadata.eval_examples_total,
6661
+ eval_examples_used: metadata.eval_examples_used
6662
+ },
6663
+ training: {
6664
+ hyperparameters: request.hyperparameters,
6665
+ exit_code: args.training.exit_code,
6666
+ log_uri: args.training.log_uri
6667
+ },
6668
+ source_fingerprint: metadata.source_fingerprint
6669
+ });
6670
+ }
5661
6671
  async function runReportStage(args) {
5662
6672
  if (!await pathExists2(args.prepared.artifacts.baselineEvalJson)) {
5663
6673
  throw new Error("Run reporting requires baseline-eval.json.");
@@ -5750,6 +6760,13 @@ async function runReportStage(args) {
5750
6760
  }
5751
6761
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
5752
6762
  const duration = elapsed(args.startedPerf);
6763
+ const provenance = await buildRunProvenance({
6764
+ prepared: args.prepared,
6765
+ config: args.config,
6766
+ training,
6767
+ recordedAt: completedAt,
6768
+ specFile: args.specFile
6769
+ });
5753
6770
  const report = runReportSchema.parse({
5754
6771
  run_id: args.prepared.request.run_id,
5755
6772
  behavior_spec_id: args.prepared.request.behavior_spec_id,
@@ -5763,6 +6780,7 @@ async function runReportStage(args) {
5763
6780
  comparison,
5764
6781
  general_regression: generalRegression,
5765
6782
  training,
6783
+ provenance,
5766
6784
  artifact_uris: {
5767
6785
  dataset: fileUri(args.prepared.artifacts.trainingJsonl),
5768
6786
  baseline_eval: fileUri(args.prepared.artifacts.baselineEvalJson),
@@ -5777,6 +6795,7 @@ async function runReportStage(args) {
5777
6795
  dataset_prebuilt: args.prepared.metadata.dataset_prebuilt,
5778
6796
  dataset_format: args.prepared.metadata.dataset_format,
5779
6797
  dataset_uri: fileUri(args.prepared.artifacts.trainingJsonl),
6798
+ ...args.prepared.request.dataset_source ? { dataset_source: args.prepared.request.dataset_source } : {},
5780
6799
  spec_example_count: args.prepared.request.spec_snapshot.examples.length,
5781
6800
  training_example_count: args.prepared.metadata.training_example_count,
5782
6801
  eval_examples_total: baseline.eval_examples_total,
@@ -5849,7 +6868,7 @@ async function runLocalPipeline(input) {
5849
6868
  store,
5850
6869
  reporter: input.reporter
5851
6870
  });
5852
- await writeJsonAtomic(join10(artifacts.runDir, "resolved-workflow.json"), {
6871
+ await writeJsonAtomic(join12(artifacts.runDir, "resolved-workflow.json"), {
5853
6872
  spec: input.projectSpec ?? {
5854
6873
  ...input.request.spec_snapshot,
5855
6874
  hyperparameters: input.request.hyperparameters,
@@ -5871,7 +6890,7 @@ async function runLocalPipeline(input) {
5871
6890
  if (step.uses === "evaluate") {
5872
6891
  const isBase = step.with.model === "base";
5873
6892
  const report = isBase ? await runBaselineStage({ prepared, config: input.config, store, reporter: input.reporter, runReporter }) : await runCandidateStage({ prepared, config: input.config, store, reporter: input.reporter, runReporter });
5874
- const reportPath = join10(artifacts.runDir, "evaluations", `${step.id}.json`);
6893
+ const reportPath = join12(artifacts.runDir, "evaluations", `${step.id}.json`);
5875
6894
  await writeJsonAtomic(reportPath, { ...report, artifact_uri: fileUri(reportPath) });
5876
6895
  outputs[step.id] = { report: { ...report, artifact_uri: fileUri(reportPath) }, reportPath };
5877
6896
  await refreshArtifactManifest(prepared);
@@ -5890,7 +6909,8 @@ async function runLocalPipeline(input) {
5890
6909
  store,
5891
6910
  reporter: input.reporter,
5892
6911
  startedAt,
5893
- startedPerf
6912
+ startedPerf,
6913
+ specFile: input.specFile
5894
6914
  });
5895
6915
  return { request: input.request, pipeline, status: "completed", outputs, report, reportPath: artifacts.runReportJson, artifactDir: artifacts.runDir };
5896
6916
  }
@@ -5948,8 +6968,8 @@ async function runLocalFineTune(input) {
5948
6968
 
5949
6969
  // src/local-runtime/project-workflow.ts
5950
6970
  import { existsSync as existsSync3 } from "fs";
5951
- import { readFile as readFile9 } from "fs/promises";
5952
- import { dirname as dirname10, join as join11, resolve as resolve14 } from "path";
6971
+ import { readFile as readFile11 } from "fs/promises";
6972
+ import { dirname as dirname10, join as join13, resolve as resolve15 } from "path";
5953
6973
  function configForSpec(spec, specPath) {
5954
6974
  return parseLocalRunnerConfig({
5955
6975
  storeRoot: process.env.TT_LOCAL_HOME?.trim() ? expandUserPath(process.env.TT_LOCAL_HOME.trim()) : DEFAULT_PROJECT_STORE_ROOT,
@@ -5960,9 +6980,9 @@ function configForSpec(spec, specPath) {
5960
6980
  async function resolveProjectConfig(specPath, configPath, warn, loaded) {
5961
6981
  const input = loaded ?? (existsSync3(specPath) ? await loadLocalRunInput(specPath) : void 0);
5962
6982
  const spec = input && input.kind !== "request" ? input.spec : void 0;
5963
- const candidate = configPath ? resolve14(configPath) : join11(dirname10(resolve14(specPath)), "local-runner.json");
6983
+ const candidate = configPath ? resolve15(configPath) : join13(dirname10(resolve15(specPath)), "local-runner.json");
5964
6984
  if (configPath || existsSync3(candidate)) {
5965
- const legacy = parseLocalRunnerConfig(JSON.parse(await readFile9(candidate, "utf8")), candidate);
6985
+ const legacy = parseLocalRunnerConfig(JSON.parse(await readFile11(candidate, "utf8")), candidate);
5966
6986
  if (spec?.runtime || spec?.evaluation) {
5967
6987
  throw new Error("Conflicting configuration sources: tunedtensor.json contains runtime/evaluation settings and a legacy runner config is present. Migrate to tunedtensor.json; do not silently override the spec.");
5968
6988
  }
@@ -6092,6 +7112,9 @@ function trainingGpu(inventory) {
6092
7112
  if (!listed.memory_total_bytes && inventory.python?.total_memory_bytes) {
6093
7113
  return { ...listed, memory_total_bytes: inventory.python.total_memory_bytes };
6094
7114
  }
7115
+ if (!listed.memory_total_bytes && (listed.unified_memory || isUnifiedGpuName(listed.name))) {
7116
+ return { ...listed, memory_total_bytes: inventory.os.total_memory_bytes, unified_memory: true };
7117
+ }
6095
7118
  return listed;
6096
7119
  }
6097
7120
  if (inventory.python?.cuda_device) {
@@ -6275,7 +7298,7 @@ function warningsFromSnapshot(report, target) {
6275
7298
  import { constants as constants2 } from "fs";
6276
7299
  import { access as access2, statfs } from "fs/promises";
6277
7300
  import { cpus, freemem, homedir as homedir5, totalmem, type } from "os";
6278
- import { dirname as dirname11, join as join12, resolve as resolve15 } from "path";
7301
+ import { dirname as dirname11, join as join14, resolve as resolve16 } from "path";
6279
7302
  import { spawn as spawn2 } from "child_process";
6280
7303
  async function runCommand(command, args, options = {}) {
6281
7304
  const timeoutMs = options.timeoutMs ?? 1e4;
@@ -6429,7 +7452,7 @@ async function queryNvidiaGpus(env) {
6429
7452
  return { result: table.code === 0 || !last ? table : last, gpus };
6430
7453
  }
6431
7454
  async function diskCheck(name, path) {
6432
- const resolvedPath = resolve15(path);
7455
+ const resolvedPath = resolve16(path);
6433
7456
  try {
6434
7457
  await access2(resolvedPath, constants2.R_OK);
6435
7458
  const fs = await statfs(resolvedPath);
@@ -6530,10 +7553,10 @@ async function collectHostInventory(options = {}) {
6530
7553
  const cwd = options.cwd ?? process.cwd();
6531
7554
  const nodeVersion = process.versions.node;
6532
7555
  const nodeMajor = Number(nodeVersion.split(".")[0]);
6533
- const artifactRoot = resolve15(cwd, config?.artifactRoot ?? join12(cwd, DEFAULT_ARTIFACT_ROOT));
6534
- const storeRoot = resolve15(config?.storeRoot ?? defaultStoreRoot(env));
6535
- const modelCache = resolve15(
6536
- config?.paths.modelCache ?? env.HF_HOME ?? join12(homedir5(), ".cache", "huggingface")
7556
+ const artifactRoot = resolve16(cwd, config?.artifactRoot ?? join14(cwd, DEFAULT_ARTIFACT_ROOT));
7557
+ const storeRoot = resolve16(config?.storeRoot ?? defaultStoreRoot(env));
7558
+ const modelCache = resolve16(
7559
+ config?.paths.modelCache ?? env.HF_HOME ?? join14(homedir5(), ".cache", "huggingface")
6537
7560
  );
6538
7561
  const [artifactDisk, storeDisk, cacheDisk, uvVersion, nvidiaQueried] = await Promise.all([
6539
7562
  diskCheck("artifact-root", artifactRoot),
@@ -6579,17 +7602,17 @@ async function collectHostInventory(options = {}) {
6579
7602
  }
6580
7603
 
6581
7604
  // src/local-runtime/hardware-snapshot.ts
6582
- import { chmod as chmod2, mkdir as mkdir10, readFile as readFile10, writeFile as writeFile7 } from "fs/promises";
6583
- import { dirname as dirname12, join as join13 } from "path";
7605
+ import { chmod as chmod2, mkdir as mkdir12, readFile as readFile12, writeFile as writeFile8 } from "fs/promises";
7606
+ import { dirname as dirname12, join as join15 } from "path";
6584
7607
  var HARDWARE_SNAPSHOT_VERSION = 1;
6585
7608
  var HARDWARE_SNAPSHOT_TTL_MS = 24 * 60 * 60 * 1e3;
6586
7609
  function hardwareSnapshotPath(env = process.env) {
6587
- return join13(getTunedTensorHome(env), "hardware.json");
7610
+ return join15(getTunedTensorHome(env), "hardware.json");
6588
7611
  }
6589
7612
  async function readHardwareSnapshot(env = process.env) {
6590
7613
  const path = hardwareSnapshotPath(env);
6591
7614
  try {
6592
- const parsed = JSON.parse(await readFile10(path, "utf8"));
7615
+ const parsed = JSON.parse(await readFile12(path, "utf8"));
6593
7616
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
6594
7617
  const snapshot = parsed;
6595
7618
  if (snapshot.version !== HARDWARE_SNAPSHOT_VERSION) return void 0;
@@ -6606,7 +7629,7 @@ async function readHardwareSnapshot(env = process.env) {
6606
7629
  }
6607
7630
  async function writeHardwareSnapshot(snapshot, env = process.env) {
6608
7631
  const path = hardwareSnapshotPath(env);
6609
- await mkdir10(dirname12(path), { recursive: true, mode: 448 });
7632
+ await mkdir12(dirname12(path), { recursive: true, mode: 448 });
6610
7633
  const gpu = snapshot.capabilities.gpu;
6611
7634
  const record = {
6612
7635
  version: HARDWARE_SNAPSHOT_VERSION,
@@ -6617,7 +7640,7 @@ async function writeHardwareSnapshot(snapshot, env = process.env) {
6617
7640
  capabilities: snapshot.capabilities,
6618
7641
  summary: snapshot.summary ?? formatCapabilitySummary(snapshot.capabilities)
6619
7642
  };
6620
- await writeFile7(path, `${JSON.stringify(record, null, 2)}
7643
+ await writeFile8(path, `${JSON.stringify(record, null, 2)}
6621
7644
  `, { mode: 384 });
6622
7645
  await chmod2(path, 384);
6623
7646
  return record;
@@ -6676,6 +7699,59 @@ async function assessHardware(options = {}) {
6676
7699
  }, env);
6677
7700
  }
6678
7701
 
7702
+ // src/local-runtime/console-reporter.ts
7703
+ function shortValue(value) {
7704
+ if (value === void 0 || value === null) return null;
7705
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
7706
+ return String(value);
7707
+ }
7708
+ if (Array.isArray(value)) return `[${value.length} items]`;
7709
+ return null;
7710
+ }
7711
+ function formatRunEvent(event) {
7712
+ const detailText = Object.entries(event.details ?? {}).filter(([key]) => key !== "metrics").map(([key, value]) => {
7713
+ const formatted = key === "command" && Array.isArray(value) ? value.join(" ") : shortValue(value);
7714
+ return formatted ? `${key}=${formatted}` : null;
7715
+ }).filter((value) => Boolean(value)).slice(0, 5).join(" ");
7716
+ return sanitizeLogLine(`[tt] ${event.stage}: ${event.message}${detailText ? ` (${detailText})` : ""}`);
7717
+ }
7718
+ function elapsedLabel(milliseconds) {
7719
+ const total = Math.max(0, Math.floor(milliseconds / 1e3));
7720
+ const hours = Math.floor(total / 3600);
7721
+ const minutes = Math.floor(total % 3600 / 60);
7722
+ const seconds = total % 60;
7723
+ return `+${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
7724
+ }
7725
+ function createRunConsoleReporter(options = {}) {
7726
+ const write = options.write ?? ((text) => {
7727
+ process.stderr.write(text);
7728
+ });
7729
+ const now = options.now ?? (() => Date.now());
7730
+ const started = now();
7731
+ const stamp = (line) => line.replace(/^\[tt\] /, `[tt] ${elapsedLabel(now() - started)} `);
7732
+ const progress = createProgressRenderer({ write, tty: progressTty(), columns: progressColumns() });
7733
+ let lastLog = "";
7734
+ return {
7735
+ verbose: options.verbose ?? false,
7736
+ onProgress(update) {
7737
+ progress.update(update);
7738
+ },
7739
+ onEvent(event) {
7740
+ progress.interrupt();
7741
+ write(`${stamp(formatRunEvent(event))}
7742
+ `);
7743
+ },
7744
+ onLog(log) {
7745
+ const line = sanitizeLogLine(`[tt] ${log.stage}${log.stream ? ` ${log.stream}` : ""}: ${log.message}`);
7746
+ if (line === lastLog) return;
7747
+ lastLog = line;
7748
+ progress.interrupt();
7749
+ write(`${stamp(line)}
7750
+ `);
7751
+ }
7752
+ };
7753
+ }
7754
+
6679
7755
  export {
6680
7756
  fileUri,
6681
7757
  assertArtifactManifest,
@@ -6702,7 +7778,12 @@ export {
6702
7778
  behaviorSpecSchema,
6703
7779
  specSnapshotSchema,
6704
7780
  foundationHyperparametersSchema,
7781
+ huggingFaceRepoIdSchema,
7782
+ datasetColumnsSchema,
7783
+ huggingFaceDatasetSourceSchema,
7784
+ isRepoRelativePath,
6705
7785
  datasetPrebuiltSchema,
7786
+ datasetSourceSchema,
6706
7787
  fineTuneHyperparametersSchema,
6707
7788
  fineTuneRunRequestSchema,
6708
7789
  evalExampleResultSchema,
@@ -6711,6 +7792,7 @@ export {
6711
7792
  evalReportSchema,
6712
7793
  comparisonReportSchema,
6713
7794
  trainingReportSchema,
7795
+ runProvenanceSchema,
6714
7796
  runReportSchema,
6715
7797
  localRunnerConfigSchema,
6716
7798
  projectRuntimeSchema,
@@ -6724,6 +7806,7 @@ export {
6724
7806
  isFoundationSpecFile,
6725
7807
  isDecisionSpecFile,
6726
7808
  normalizeChatJsonlForRelocation,
7809
+ datasetInputIdentity,
6727
7810
  buildSystemMessage,
6728
7811
  exampleToChatRow,
6729
7812
  compileSpecToJsonl,
@@ -6772,6 +7855,7 @@ export {
6772
7855
  withOfflineHuggingFaceCacheEnvironment,
6773
7856
  checkAwsGpu,
6774
7857
  runGpuProcess,
7858
+ auditRun,
6775
7859
  isTerminalRunState,
6776
7860
  defaultLocalHome,
6777
7861
  localStorePaths,
@@ -6784,8 +7868,13 @@ export {
6784
7868
  compareEvalReports,
6785
7869
  verifyModelPrefetchCacheReport,
6786
7870
  buildModelPrefetchPayload,
7871
+ safeName,
6787
7872
  verifyLocalBaseModel,
6788
7873
  prefetchBaseModel,
7874
+ DatasetNotDownloadedError,
7875
+ huggingFaceDataset,
7876
+ prefetchHuggingFaceDataset,
7877
+ ensureDatasetCached,
6789
7878
  evaluateGeneralRegressionGate,
6790
7879
  canonicalLocalPipeline,
6791
7880
  validateLocalPipeline,
@@ -6804,6 +7893,7 @@ export {
6804
7893
  readHardwareSnapshot,
6805
7894
  formatHostStatusLine,
6806
7895
  formatAgentHostBlock,
6807
- assessHardware
7896
+ assessHardware,
7897
+ createRunConsoleReporter
6808
7898
  };
6809
- //# sourceMappingURL=chunk-PVLX7LJI.js.map
7899
+ //# sourceMappingURL=chunk-4SUXBHIY.js.map