@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.
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  TRAINING_MODELS,
9
9
  assertFoundationSettingsMatch,
10
10
  assessHardware,
11
+ auditRun,
11
12
  buildDecisionPythonCommand,
12
13
  buildFoundationPythonCommand,
13
14
  buildSystemMessage,
@@ -18,10 +19,12 @@ import {
18
19
  configForSpec,
19
20
  createExecutionPlan,
20
21
  createLocalStore,
22
+ createRunConsoleReporter,
21
23
  decisionLabels,
22
24
  defaultLocalHome,
23
25
  defaultStoreRoot,
24
26
  deriveSampleSeed,
27
+ ensureDatasetCached,
25
28
  evalReportSchema,
26
29
  evaluateCapabilities,
27
30
  expandUserPath,
@@ -55,7 +58,7 @@ import {
55
58
  warningsFromSnapshot,
56
59
  withDecisionPythonEnvironment,
57
60
  withFoundationPythonEnvironment
58
- } from "./chunk-PVLX7LJI.js";
61
+ } from "./chunk-4SUXBHIY.js";
59
62
 
60
63
  // src/cli.ts
61
64
  import { spawn as spawn4 } from "child_process";
@@ -2163,7 +2166,9 @@ function createTunedTensorTools(api, options = {}) {
2163
2166
  runtime: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
2164
2167
  evaluation: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
2165
2168
  pipeline: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
2166
- dataset_prebuilt: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown()))
2169
+ dataset_prebuilt: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown(), {
2170
+ description: "Local chat JSONL files ({training, validation|test}), or a Hugging Face dataset: {huggingface: {repo, revision?: 40-char commit, columns?: {input, output}}, training: 'repo/path.jsonl', test|validation: ...}. The user downloads it with `tt datasets prefetch tunedtensor.json`."
2171
+ }))
2167
2172
  }, { additionalProperties: false })
2168
2173
  }, { additionalProperties: false }),
2169
2174
  async (p) => {
@@ -3752,6 +3757,8 @@ function gpuDetail(gpu) {
3752
3757
  } else if (gpu.memory_total_bytes !== void 0) {
3753
3758
  const used = gpu.memory_used_bytes;
3754
3759
  parts.push(used === void 0 ? `${formatGiB(gpu.memory_total_bytes)} GiB VRAM` : `VRAM ${formatGiB(used)}/${formatGiB(gpu.memory_total_bytes)} GiB`);
3760
+ } else if (gpu.unified_memory) {
3761
+ parts.push("unified with system RAM");
3755
3762
  }
3756
3763
  if (gpu.temperature_c !== void 0) parts.push(`${Math.round(gpu.temperature_c)}\xB0C`);
3757
3764
  return parts.join(" ");
@@ -3903,6 +3910,7 @@ function renderBanner(input) {
3903
3910
 
3904
3911
  // src/local-runtime/live-usage.ts
3905
3912
  import { execFile } from "child_process";
3913
+ import { readFileSync as readFileSync5 } from "fs";
3906
3914
  import { statfs } from "fs/promises";
3907
3915
  import { cpus, freemem, homedir, loadavg, totalmem } from "os";
3908
3916
  import { dirname as dirname3, join as join7, resolve as resolve2 } from "path";
@@ -3974,6 +3982,50 @@ function queryNvidia(env, timeoutMs) {
3974
3982
  function tidyCpuModel(model) {
3975
3983
  return model.replace(/\((?:R|TM|tm|r)\)/g, "").replace(/\s+(?:CPU|Processor)\b/gi, "").replace(/\s*@\s*[\d.]+\s*[GM]Hz/i, "").replace(/\s+\d+-Core$/i, "").replace(/\s+/g, " ").trim() || "CPU";
3976
3984
  }
3985
+ var ARM_CORES = {
3986
+ "0xd03": "Cortex-A53",
3987
+ "0xd05": "Cortex-A55",
3988
+ "0xd08": "Cortex-A72",
3989
+ "0xd0b": "Cortex-A76",
3990
+ "0xd0c": "Neoverse-N1",
3991
+ "0xd40": "Neoverse-V1",
3992
+ "0xd41": "Cortex-A78",
3993
+ "0xd44": "Cortex-X1",
3994
+ "0xd47": "Cortex-A710",
3995
+ "0xd48": "Cortex-X2",
3996
+ "0xd49": "Neoverse-N2",
3997
+ "0xd4d": "Cortex-A715",
3998
+ "0xd4e": "Cortex-X3",
3999
+ "0xd4f": "Neoverse-V2",
4000
+ "0xd80": "Cortex-A520",
4001
+ "0xd81": "Cortex-A720",
4002
+ "0xd82": "Cortex-X4",
4003
+ "0xd84": "Neoverse-V3",
4004
+ "0xd85": "Cortex-X925",
4005
+ "0xd87": "Cortex-A725",
4006
+ "0xd8e": "Neoverse-N3"
4007
+ };
4008
+ function armCpuModel(cpuinfo) {
4009
+ const names = [];
4010
+ let implementer;
4011
+ for (const line of cpuinfo.split(/\r?\n/)) {
4012
+ const [key, value] = line.split(":").map((item) => item.trim().toLowerCase());
4013
+ if (key === "cpu implementer") implementer = value;
4014
+ if (key !== "cpu part" || !value) continue;
4015
+ const name = implementer === "0x41" ? ARM_CORES[value] : void 0;
4016
+ if (name && !names.includes(name)) names.push(name);
4017
+ }
4018
+ return names.length > 0 ? names.join(" + ") : void 0;
4019
+ }
4020
+ function cpuModel(reported, platform, arch) {
4021
+ const model = tidyCpuModel(reported ?? "");
4022
+ if (platform !== "linux" || !/^(?:unknown|CPU)$/i.test(model)) return model;
4023
+ try {
4024
+ return armCpuModel(readFileSync5("/proc/cpuinfo", "utf8")) ?? (arch === "arm64" ? "Arm CPU" : model);
4025
+ } catch {
4026
+ return arch === "arm64" ? "Arm CPU" : model;
4027
+ }
4028
+ }
3977
4029
  async function modelCacheDisk(env) {
3978
4030
  const path = resolve2(env.HF_HOME?.trim() || join7(homedir(), ".cache", "huggingface"));
3979
4031
  for (let probe = path, depth = 0; depth < 8; depth += 1) {
@@ -3992,9 +4044,9 @@ async function modelCacheDisk(env) {
3992
4044
  }
3993
4045
  return void 0;
3994
4046
  }
3995
- function appleGpu(cpuModel, platform, arch) {
4047
+ function appleGpu(cpuModel2, platform, arch) {
3996
4048
  if (platform !== "darwin" || arch !== "arm64") return void 0;
3997
- const chip = /apple/i.test(cpuModel) ? cpuModel.trim() : "Apple Silicon";
4049
+ const chip = /apple/i.test(cpuModel2) ? cpuModel2.trim() : "Apple Silicon";
3998
4050
  return {
3999
4051
  index: 0,
4000
4052
  name: `${chip} GPU`,
@@ -4015,7 +4067,7 @@ async function sampleLiveUsage(options = {}) {
4015
4067
  ]);
4016
4068
  const after = cpuTimes();
4017
4069
  const list = cpus();
4018
- const model = tidyCpuModel(list[0]?.model ?? "");
4070
+ const model = cpuModel(list[0]?.model, platform, arch);
4019
4071
  const total = totalmem();
4020
4072
  const apple = nvidia.gpus.length === 0 ? appleGpu(model, platform, arch) : void 0;
4021
4073
  const [one, five, fifteen] = loadavg();
@@ -4061,12 +4113,15 @@ var COMMAND_CATALOG = [
4061
4113
  { path: "runs report", description: "Show a local run report.", group: "Inspect", modes: LOCAL },
4062
4114
  { path: "runs compare", description: "Compare two local run reports.", group: "Inspect", modes: LOCAL },
4063
4115
  { path: "runs events", description: "Show local run progress events.", group: "Inspect", modes: LOCAL },
4116
+ { path: "runs audit", description: "Verify a run's event log and report, with timeline and provenance.", group: "Inspect", modes: LOCAL },
4064
4117
  { path: "models list", description: "List local models.", group: "Inspect", modes: LOCAL },
4065
4118
  { path: "models get", description: "Show a local model.", group: "Inspect", modes: LOCAL },
4066
4119
  { path: "models serve", description: "Serve a model through an OpenAI-compatible API.", group: "Serving", modes: LOCAL },
4067
4120
  { path: "models verify", description: "Verify a local model artifact.", group: "Serving", modes: LOCAL },
4068
4121
  { path: "models prefetch", description: "Download the local base-model snapshot.", group: "Serving", modes: LOCAL },
4069
4122
  { path: "models verify-base", description: "Verify the local base-model snapshot.", group: "Serving", modes: LOCAL },
4123
+ { path: "datasets prefetch", description: "Download the spec's Hugging Face dataset files.", group: "Workflow", modes: LOCAL },
4124
+ { path: "datasets verify", description: "Verify the cached Hugging Face dataset files.", group: "Workflow", modes: LOCAL },
4070
4125
  { path: "models active", description: "Show the active local model.", group: "Serving", modes: LOCAL },
4071
4126
  { path: "models activate", description: "Activate a verified local model.", group: "Serving", modes: LOCAL },
4072
4127
  { path: "models rollback", description: "Roll back the active local model.", group: "Serving", modes: LOCAL },
@@ -6385,10 +6440,22 @@ function renderValidate(value) {
6385
6440
  ["Spec", text(value.behavior_spec_id)],
6386
6441
  ["Base model", text(value.base_model)],
6387
6442
  ["Dataset", text(value.dataset_format)],
6443
+ ["Hugging Face", huggingFaceDatasetLabel(value)],
6388
6444
  ["Artifacts", text(value.artifact_root)],
6389
6445
  ["Store", text(value.store_root)],
6390
6446
  ["Dry run", text(value.dry_run)]
6391
6447
  ]);
6448
+ if (value.dataset_status === "not_downloaded") {
6449
+ printWarning(`Dataset files are not downloaded yet; dataset contents were not checked. Run \`${text(value.next_step) ?? "tt datasets prefetch"}\`.`);
6450
+ }
6451
+ }
6452
+ function huggingFaceDatasetLabel(value) {
6453
+ const source = record3(value.dataset_source) ?? record3(value.dataset_huggingface);
6454
+ const repo = text(source?.repo);
6455
+ if (!repo) return void 0;
6456
+ const revision = text(source?.revision) ?? "main";
6457
+ const status2 = value.dataset_status === "not_downloaded" ? "not downloaded" : "cached";
6458
+ return `${repo}@${revision} (${status2}${source?.pinned === false || !text(source?.revision) ? ", unpinned" : ""})`;
6392
6459
  }
6393
6460
  function renderRun(value) {
6394
6461
  const runStatus = text(value.status) ?? "unknown";
@@ -6443,6 +6510,68 @@ function renderRunEvents(value) {
6443
6510
  ])
6444
6511
  );
6445
6512
  }
6513
+ function formatSeconds(value) {
6514
+ if (typeof value !== "number" || !Number.isFinite(value)) return "running";
6515
+ const seconds = Math.round(value);
6516
+ const hours = Math.floor(seconds / 3600);
6517
+ const minutes = Math.floor(seconds % 3600 / 60);
6518
+ const rest = seconds % 60;
6519
+ if (hours) return `${hours}h ${String(minutes).padStart(2, "0")}m`;
6520
+ if (minutes) return `${minutes}m ${String(rest).padStart(2, "0")}s`;
6521
+ return `${rest}s`;
6522
+ }
6523
+ function shortHash(value) {
6524
+ const hash = text(value);
6525
+ return hash ? hash.slice(0, 12) : void 0;
6526
+ }
6527
+ function renderRunAudit(value) {
6528
+ const verdict = text(value.verdict);
6529
+ const chain = record3(value.chain);
6530
+ const reportInfo = record3(value.report);
6531
+ const findings = Array.isArray(value.findings) ? value.findings.map(String) : [];
6532
+ if (verdict === "verified") printSuccess("Run evidence verified: event log intact and report unchanged since completion");
6533
+ else if (verdict === "tampered") printError("Run evidence does not verify");
6534
+ else printWarning("Run evidence is incomplete");
6535
+ for (const finding of findings) console.log(` - ${finding}`);
6536
+ printDetail([
6537
+ ["Run", text(value.run_id)],
6538
+ ["Status", status(value.status)],
6539
+ ["Event chain", `${text(chain?.status) ?? "\u2014"} (${text(chain?.chained_events) ?? 0}/${text(chain?.events) ?? 0} chained, head ${shortHash(chain?.head_hash) ?? "\u2014"})`],
6540
+ ["Report sha256", shortHash(reportInfo?.recorded_sha256) ?? "not recorded"]
6541
+ ]);
6542
+ const timeline = rows(value.timeline);
6543
+ if (timeline.length > 0) {
6544
+ console.log("");
6545
+ printTable(
6546
+ ["Started", "Stage", "Duration", "Status"],
6547
+ timeline.map((entry) => [
6548
+ formatDate(text(entry.started_at)),
6549
+ text(entry.stage) ?? "\u2014",
6550
+ formatSeconds(entry.duration_seconds),
6551
+ status(entry.status)
6552
+ ])
6553
+ );
6554
+ }
6555
+ const provenance = record3(value.provenance);
6556
+ if (provenance) {
6557
+ const software = record3(provenance.software);
6558
+ const execution = record3(provenance.execution);
6559
+ const spec = record3(provenance.spec);
6560
+ const baseModel = record3(provenance.base_model);
6561
+ const data3 = record3(provenance.data);
6562
+ const files = record3(data3?.file_sha256) ?? {};
6563
+ console.log("");
6564
+ printDetail([
6565
+ ["TT", `${text(software?.tt_version) ?? "\u2014"} (node ${text(software?.node_version) ?? "\u2014"}, ${text(software?.platform) ?? "\u2014"}/${text(software?.arch) ?? "\u2014"})`],
6566
+ ["Execution", `${text(execution?.target) ?? "\u2014"}${text(execution?.instance_id) ? ` ${text(execution?.instance_id)}` : ""} via ${text(execution?.training_backend) ?? "\u2014"}`],
6567
+ ["Spec", `${text(spec?.file_path) ?? text(spec?.behavior_spec_id) ?? "\u2014"} (sha256 ${shortHash(spec?.file_sha256) ?? shortHash(spec?.snapshot_sha256) ?? "\u2014"})`],
6568
+ ["Base model", `${text(baseModel?.id) ?? "\u2014"}@${shortHash(baseModel?.revision) ?? "unpinned"}`],
6569
+ ...Object.entries(files).map(([split, hash]) => [`Data ${split}`, `sha256 ${shortHash(hash)}`]),
6570
+ ["Training data", `sha256 ${shortHash(data3?.compiled_training_sha256) ?? "\u2014"} (${text(data3?.training_examples) ?? "\u2014"} examples)`],
6571
+ ["Evaluation", `${text(data3?.eval_examples_used) ?? "\u2014"}/${text(data3?.eval_examples_total) ?? "\u2014"} ${text(data3?.eval_split) ?? ""} (seed ${text(data3?.eval_sample_seed) ?? "\u2014"})`]
6572
+ ]);
6573
+ }
6574
+ }
6446
6575
  function renderRunRecord(value) {
6447
6576
  printDetail([
6448
6577
  ["Run", text(value.id) ?? text(value.run_id)],
@@ -6548,6 +6677,22 @@ function renderModelPrefetch(value) {
6548
6677
  ["Cache", text(value.model_cache) ?? text(value.hf_home)]
6549
6678
  ]);
6550
6679
  }
6680
+ function renderDatasetPrefetch(value) {
6681
+ printSuccess(
6682
+ value.local_files_only === true ? "Verified the cached Hugging Face dataset" : "Dataset is ready locally"
6683
+ );
6684
+ const files = record3(value.files) ?? {};
6685
+ printDetail([
6686
+ ["Dataset", text(value.repo)],
6687
+ ["Revision", `${text(value.revision) ?? "\u2014"}${value.pinned === false ? " (unpinned: set dataset_prebuilt.huggingface.revision to pin it)" : ""}`],
6688
+ ...Object.entries(files).map(([split, file]) => {
6689
+ const entry = record3(file);
6690
+ return [split[0].toUpperCase() + split.slice(1), `${text(entry?.path) ?? "\u2014"} (${displayBytes(entry?.size_bytes) ?? "\u2014"}, sha256 ${String(text(entry?.sha256) ?? "").slice(0, 12)})`];
6691
+ }),
6692
+ ["Snapshot", text(value.snapshot_path)],
6693
+ ["Cache", text(value.hub_cache)]
6694
+ ]);
6695
+ }
6551
6696
  function renderActiveModel(value) {
6552
6697
  const active = text(value.active) ?? "base";
6553
6698
  const pointer = record3(value.pointer);
@@ -6649,6 +6794,10 @@ function renderKnownJson(args2, value) {
6649
6794
  renderRunEvents(value);
6650
6795
  return true;
6651
6796
  }
6797
+ if (subcommand === "audit" && isRecord4(value)) {
6798
+ renderRunAudit(value);
6799
+ return true;
6800
+ }
6652
6801
  if (subcommand === "get" && isRecord4(value)) {
6653
6802
  renderRunRecord(value);
6654
6803
  return true;
@@ -6685,6 +6834,10 @@ function renderKnownJson(args2, value) {
6685
6834
  return true;
6686
6835
  }
6687
6836
  }
6837
+ if (command === "datasets" && isRecord4(value)) {
6838
+ renderDatasetPrefetch(value);
6839
+ return true;
6840
+ }
6688
6841
  if (command === "serve" && isRecord4(value)) {
6689
6842
  renderServePlan(value);
6690
6843
  return true;
@@ -6862,6 +7015,14 @@ function selectedSpecArgument(args2, cwd) {
6862
7015
  if (command === "models" && ["prefetch", "verify-base"].includes(args2[1] ?? "")) {
6863
7016
  return positionalSpecArgument(args2, 2, join9(cwd, DEFAULT_SPEC_NAME));
6864
7017
  }
7018
+ if (command === "datasets") {
7019
+ if (args2[1] === void 0 || args2[1].startsWith("-") || args2[1].endsWith(".json")) {
7020
+ args2.splice(1, 0, "prefetch");
7021
+ }
7022
+ if (["prefetch", "verify"].includes(args2[1] ?? "")) {
7023
+ return positionalSpecArgument(args2, 2, join9(cwd, DEFAULT_SPEC_NAME));
7024
+ }
7025
+ }
6865
7026
  const serves = command === "serve" || command === "models" && args2[1] === "serve";
6866
7027
  if (!serves) return null;
6867
7028
  const explicit = optionValueArgument(args2, "--spec");
@@ -7002,6 +7163,8 @@ function normalizeProjectedPaths(value, replacements) {
7002
7163
  async function spawnLocalCli(args2) {
7003
7164
  const output = args2.stdout ?? process.stdout;
7004
7165
  const errors = args2.stderr ?? process.stderr;
7166
+ const errorTerminal = errors;
7167
+ const childEnv = errorTerminal.isTTY && !args2.jsonMode && args2.env.TT_PROGRESS_TTY === void 0 ? { ...args2.env, TT_PROGRESS_TTY: "1", TT_PROGRESS_COLUMNS: String(errorTerminal.columns ?? 100) } : args2.env;
7005
7168
  const pipeInput = Boolean(args2.stdin && args2.stdin !== process.stdin);
7006
7169
  const pipeStreamingOutput = args2.streamingStdout && Boolean(args2.stdout && args2.stdout !== process.stdout);
7007
7170
  const child = spawn(
@@ -7009,7 +7172,7 @@ async function spawnLocalCli(args2) {
7009
7172
  [args2.entrypoint, ...args2.commandArgs],
7010
7173
  {
7011
7174
  cwd: args2.cwd,
7012
- env: args2.env,
7175
+ env: childEnv,
7013
7176
  stdio: [
7014
7177
  pipeInput ? "pipe" : "inherit",
7015
7178
  args2.streamingStdout && !pipeStreamingOutput ? "inherit" : "pipe",
@@ -7173,7 +7336,8 @@ var LOCAL_COMMANDS = [
7173
7336
  "validate",
7174
7337
  "serve",
7175
7338
  "runs",
7176
- "models"
7339
+ "models",
7340
+ "datasets"
7177
7341
  ];
7178
7342
  var LOCAL_COMMAND_DESCRIPTIONS = {
7179
7343
  info: "Show the installed local runtime version and runner status",
@@ -7183,7 +7347,8 @@ var LOCAL_COMMAND_DESCRIPTIONS = {
7183
7347
  validate: "Validate a local behavior spec without executing it",
7184
7348
  serve: "Serve a verified adapter, the active model, or the protected base",
7185
7349
  runs: "Inspect locally stored runs",
7186
- models: "Inspect, verify, prefetch, or serve local models"
7350
+ models: "Inspect, verify, prefetch, or serve local models",
7351
+ datasets: "Download or verify the Hugging Face dataset a spec trains on"
7187
7352
  };
7188
7353
  function applyExitCode(result2) {
7189
7354
  if (result2?.exitCode !== null && result2?.exitCode !== void 0) {
@@ -7298,7 +7463,7 @@ function registerAgentCommands(parent, options) {
7298
7463
  }
7299
7464
 
7300
7465
  // src/commands/pipeline.ts
7301
- import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
7466
+ import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
7302
7467
  import { dirname as dirname9, join as join13, resolve as resolve8 } from "path";
7303
7468
 
7304
7469
  // src/local-runtime/project-migration.ts
@@ -8186,7 +8351,7 @@ function loadPipelineFile(path) {
8186
8351
  const resolved = resolve8(path);
8187
8352
  if (!existsSync4(resolved)) throw new Error(`Pipeline file not found: ${path}. Run \`tt pipeline init\` to create one.`);
8188
8353
  try {
8189
- return JSON.parse(readFileSync5(resolved, "utf8"));
8354
+ return JSON.parse(readFileSync6(resolved, "utf8"));
8190
8355
  } catch (error) {
8191
8356
  throw new Error(`Pipeline file must be JSON: ${error.message}`);
8192
8357
  }
@@ -8202,7 +8367,7 @@ function resolvePipelineDocument(options, specRequired) {
8202
8367
  if (requested !== void 0) return { document: parsePipeline(requested) };
8203
8368
  throw new Error(`Behavior spec not found: ${options.spec}. Create tunedtensor.json or pass an explicit --file for a recipe-only preview.`);
8204
8369
  }
8205
- const source = readFileSync5(specPath, "utf8");
8370
+ const source = readFileSync6(specPath, "utf8");
8206
8371
  const input = parseLocalRunInput(JSON.parse(source), specPath);
8207
8372
  const document = pipelineForRunInput(input, requested);
8208
8373
  const spec = {
@@ -8260,7 +8425,7 @@ function registerPipelineCommands(parent) {
8260
8425
  pipeline2.command("init").description("Write a canonical v1 pipeline recipe").option("-f, --file <path>", "Legacy standalone recipe output (default: embed in tunedtensor.json)").option("--engine <engine>", "adapter (default) or foundation").option("--spec <path>", "Foundation spec whose hyperparameters stamp the DAG").action(async (options) => {
8261
8426
  if (!options.file) {
8262
8427
  const path2 = resolve8(options.spec ?? DEFAULT_SPEC_FILE);
8263
- const raw = JSON.parse(readFileSync5(path2, "utf8"));
8428
+ const raw = JSON.parse(readFileSync6(path2, "utf8"));
8264
8429
  const input = parseLocalRunInput(raw, path2);
8265
8430
  if (input.kind === "request") throw new Error("Pipeline init requires a behavior spec.");
8266
8431
  if (options.engine && options.engine !== (input.spec.engine ?? "adapter")) throw new Error("--engine must match the spec.");
@@ -8324,7 +8489,7 @@ function registerPipelineCommands(parent) {
8324
8489
  const plan = createExecutionPlan(document, { only: parseList(options.only), skip: parseList(options.skip) });
8325
8490
  outputPlan({ ...plan, ...spec ? { spec } : {} }, await hostWarningsForPipeline(document, input));
8326
8491
  });
8327
- pipeline2.command("run").description("Run an ordered local pipeline, or safely preview any pipeline").option("--dry-run", "Resolve and display only; never execute, transfer, or reserve credits").option("-f, --file <path>", "Explicit pipeline recipe (default: derive from behavior spec)").option("--spec <path>", "Local behavior spec", DEFAULT_SPEC_FILE).option("--config <path>", "Local runtime config").option("--output <path>", "Foundation or decision run directory (must not already exist)").option("--resume <path>", "Resume a foundation run directory").option("--only <ids>", "Comma-separated step IDs to include").option("--skip <ids>", "Comma-separated step IDs to omit").action(async (options, command) => {
8492
+ pipeline2.command("run").description("Run an ordered local pipeline, or safely preview any pipeline").option("--dry-run", "Resolve and display only; never execute, transfer, or reserve credits").option("-f, --file <path>", "Explicit pipeline recipe (default: derive from behavior spec)").option("--spec <path>", "Local behavior spec", DEFAULT_SPEC_FILE).option("--config <path>", "Local runtime config").option("--output <path>", "Foundation or decision run directory (must not already exist)").option("--resume <path>", "Resume a foundation run directory").option("--only <ids>", "Comma-separated step IDs to include").option("--skip <ids>", "Comma-separated step IDs to omit").option("--verbose", "Also stream training and evaluation process logs").option("--quiet", "Do not print live stage progress").action(async (options, command) => {
8328
8493
  if (options.output && options.resume) {
8329
8494
  throw new Error("--output and --resume are mutually exclusive.");
8330
8495
  }
@@ -8387,7 +8552,19 @@ function registerPipelineCommands(parent) {
8387
8552
  ...plan.name ? { name: plan.name } : {},
8388
8553
  steps: plan.steps.map(({ transfers: _transfers, ...step }) => step)
8389
8554
  };
8390
- const result2 = await runLocalPipeline({ request: input.request, config, pipeline: localPipeline, ...input.kind === "spec" ? { projectSpec: input.spec } : {} });
8555
+ const reporter = options.quiet ? void 0 : createRunConsoleReporter({ verbose: options.verbose });
8556
+ const { request: request2 } = await ensureDatasetCached({ request: input.request, config, reporter });
8557
+ const result2 = await runLocalPipeline({
8558
+ request: request2,
8559
+ config,
8560
+ pipeline: localPipeline,
8561
+ ...input.kind === "spec" ? { projectSpec: input.spec } : {},
8562
+ ...spec ? { specFile: { path: spec.path, sha256: spec.sha256 } } : {},
8563
+ ...reporter ? { reporter } : {}
8564
+ });
8565
+ if (!isJsonMode()) {
8566
+ console.error(`Run ${result2.request.run_id}: \`tt runs audit ${result2.request.run_id}\` verifies its record and shows timing and provenance.`);
8567
+ }
8391
8568
  if (isJsonMode()) return printJson({ ...result2, spec });
8392
8569
  printSuccess(`Pipeline completed with status ${result2.status}.`);
8393
8570
  });
@@ -8660,7 +8837,7 @@ function registerTopupCommands(parent) {
8660
8837
 
8661
8838
  // src/commands/publish.ts
8662
8839
  import { createInterface as createInterface4 } from "readline/promises";
8663
- import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
8840
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
8664
8841
  import { homedir as homedir3 } from "os";
8665
8842
  import { isAbsolute as isAbsolute5, join as join14, resolve as resolve9 } from "path";
8666
8843
  import { stdin as defaultStdin, stdout as defaultStdout } from "process";
@@ -8677,13 +8854,13 @@ function resolvePublishStoreRoot(options) {
8677
8854
  const localConfigPath = join14(cwd, "local-runner.json");
8678
8855
  const projectPath = join14(cwd, "tunedtensor.json");
8679
8856
  if (existsSync5(projectPath)) {
8680
- const spec = localBehaviorSpecFileSchema.parse(JSON.parse(readFileSync6(projectPath, "utf8")));
8857
+ const spec = localBehaviorSpecFileSchema.parse(JSON.parse(readFileSync7(projectPath, "utf8")));
8681
8858
  if (existsSync5(localConfigPath) && (spec.runtime || spec.evaluation)) throw new Error("Conflicting configuration sources; migrate local-runner.json first.");
8682
8859
  if (!existsSync5(localConfigPath)) return expandPath2(spec.runtime?.storeRoot ?? (env.TT_LOCAL_HOME?.trim() || DEFAULT_PROJECT_STORE_ROOT), cwd, homeDirectory);
8683
8860
  }
8684
8861
  if (existsSync5(localConfigPath)) {
8685
8862
  try {
8686
- const raw = JSON.parse(readFileSync6(localConfigPath, "utf-8"));
8863
+ const raw = JSON.parse(readFileSync7(localConfigPath, "utf-8"));
8687
8864
  if (typeof raw.storeRoot === "string" && raw.storeRoot.trim()) {
8688
8865
  return expandPath2(raw.storeRoot, cwd, homeDirectory);
8689
8866
  }
@@ -8747,7 +8924,7 @@ function assertPublishPayloadSize(payload) {
8747
8924
  }
8748
8925
  }
8749
8926
  function registerPublishCommand(parent) {
8750
- parent.command("publish").description("Publish local run evidence to the Tuned Tensor dashboard").argument("[runId]", "Local run id or prefix (defaults to latest completed/failed run)").option("-k, --api-key <key>", "API key (tt_...)").option("-u, --base-url <url>", "API base URL").option("-y, --yes", "Skip confirmation prompt").option("--dry-run", "Show what would be published without uploading").action(async (runId, cmdOpts) => {
8927
+ parent.command("publish").description("Publish local run evidence to the Tuned Tensor dashboard").argument("[runId]", "Local run id or prefix (defaults to latest completed/failed run)").option("-k, --api-key <key>", "API key (tt_...)").option("-u, --base-url <url>", "API base URL").option("-y, --yes", "Skip confirmation prompt").option("--dry-run", "Show what would be published without uploading").option("--allow-unverified", "Publish evidence that cannot be fully verified (older TT versions, unreadable events)").action(async (runId, cmdOpts) => {
8751
8928
  const inherited = parent.optsWithGlobals();
8752
8929
  const opts = {
8753
8930
  apiKey: cmdOpts.apiKey ?? inherited.apiKey,
@@ -8766,12 +8943,27 @@ function registerPublishCommand(parent) {
8766
8943
  const report = runReportSchema.parse(rawReport);
8767
8944
  const specRecord = await store.getSpec(run.behavior_spec_id);
8768
8945
  const exampleCount = report.candidate.results.length || report.baseline.results.length;
8946
+ const audit = await auditRun(store, run.id);
8947
+ if (audit.verdict === "tampered") {
8948
+ throw new Error(
8949
+ `Run ${run.id} evidence does not verify: ${audit.findings.join(" ")} Inspect it with \`tt runs audit ${run.id}\`; tampered runs are not published.`
8950
+ );
8951
+ }
8769
8952
  const payload = {
8770
8953
  spec: {
8771
8954
  id: specRecord.id,
8772
8955
  ...specRecord.spec
8773
8956
  },
8774
- report
8957
+ report: {
8958
+ ...report,
8959
+ audit: {
8960
+ verdict: audit.verdict,
8961
+ findings: audit.findings,
8962
+ event_chain: { status: audit.chain.status, events: audit.chain.events, head_hash: audit.chain.head_hash },
8963
+ report_sha256: audit.report.recorded_sha256,
8964
+ timeline: audit.timeline
8965
+ }
8966
+ }
8775
8967
  };
8776
8968
  assertPublishPayloadSize(payload);
8777
8969
  if (isJsonMode() && cmdOpts.dryRun) {
@@ -8799,6 +8991,14 @@ function registerPublishCommand(parent) {
8799
8991
  printWarning(
8800
8992
  "Prompts and model outputs from the run report will be uploaded to your Tuned Tensor account."
8801
8993
  );
8994
+ if (audit.verdict !== "verified") {
8995
+ printWarning(`Run evidence is unverified: ${audit.findings.join(" ")}`);
8996
+ }
8997
+ }
8998
+ if (audit.verdict !== "verified" && !cmdOpts.allowUnverified && !cmdOpts.dryRun) {
8999
+ throw new Error(
9000
+ `Run ${run.id} evidence cannot be fully verified: ${audit.findings.join(" ")} Review it with \`tt runs audit\`, then pass --allow-unverified to publish it anyway; the dashboard will show it as unverified.`
9001
+ );
8802
9002
  }
8803
9003
  if (cmdOpts.dryRun) {
8804
9004
  if (isJsonMode()) {
@@ -8843,7 +9043,7 @@ function registerPublishCommand(parent) {
8843
9043
  }
8844
9044
 
8845
9045
  // src/commands/specs.ts
8846
- import { readFileSync as readFileSync7 } from "fs";
9046
+ import { readFileSync as readFileSync8 } from "fs";
8847
9047
 
8848
9048
  // src/resolve.ts
8849
9049
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -8947,7 +9147,7 @@ var SpecBodyError = class extends Error {
8947
9147
  function loadSpecBody(filePath, mode) {
8948
9148
  let parsed;
8949
9149
  try {
8950
- parsed = JSON.parse(readFileSync7(filePath, "utf-8"));
9150
+ parsed = JSON.parse(readFileSync8(filePath, "utf-8"));
8951
9151
  } catch (e) {
8952
9152
  throw new SpecBodyError(
8953
9153
  `Could not parse ${filePath}: ${e.message}`
@@ -9076,7 +9276,7 @@ function registerSpecsCommands(parent) {
9076
9276
  }
9077
9277
 
9078
9278
  // src/commands/datasets.ts
9079
- import { existsSync as existsSync6, readFileSync as readFileSync8, statSync as statSync2 } from "fs";
9279
+ import { existsSync as existsSync6, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
9080
9280
  var DOCUMENT_OCR_MIME_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp"]);
9081
9281
  function formatBytes(bytes) {
9082
9282
  if (bytes < 1024) return `${bytes} B`;
@@ -9206,7 +9406,7 @@ function parseDatasetFormat(value) {
9206
9406
  throw new Error("--format must be one of: jsonl, document_ocr_jsonl");
9207
9407
  }
9208
9408
  function validateDatasetFile(file, requestedFormat) {
9209
- const lines = readFileSync8(file, "utf8").split(/\r?\n/);
9409
+ const lines = readFileSync9(file, "utf8").split(/\r?\n/);
9210
9410
  const errors = [];
9211
9411
  let rowCount = 0;
9212
9412
  let detectedFormat = null;
@@ -9338,7 +9538,7 @@ function registerDatasetsCommands(parent) {
9338
9538
  }
9339
9539
  const format = validateDatasetFile(file, parseDatasetFormat(cmdOpts.format));
9340
9540
  const name = cmdOpts.name || file.split("/").pop().replace(/\.jsonl?$/, "");
9341
- const fileBytes = readFileSync8(file);
9541
+ const fileBytes = readFileSync9(file);
9342
9542
  const uploadUrl = await post(
9343
9543
  "/datasets/upload-url",
9344
9544
  {
@@ -9383,7 +9583,7 @@ function registerDatasetsCommands(parent) {
9383
9583
  }
9384
9584
 
9385
9585
  // src/commands/label.ts
9386
- import { existsSync as existsSync7, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
9586
+ import { existsSync as existsSync7, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
9387
9587
  function formatCents2(cents) {
9388
9588
  return `$${(cents / 100).toFixed(2)}`;
9389
9589
  }
@@ -9427,7 +9627,7 @@ function validateUnlabeledFile(file, inputColumn) {
9427
9627
  "CSV uploads need --input-column <name> to say which column holds the input text."
9428
9628
  );
9429
9629
  }
9430
- const firstLine = readFileSync9(file, "utf8").split(/\r?\n/, 1)[0] ?? "";
9630
+ const firstLine = readFileSync10(file, "utf8").split(/\r?\n/, 1)[0] ?? "";
9431
9631
  const columns = parseCsvHeader(firstLine);
9432
9632
  if (!columns.includes(inputColumn)) {
9433
9633
  throw new Error(
@@ -9439,7 +9639,7 @@ function validateUnlabeledFile(file, inputColumn) {
9439
9639
  if (!lower.endsWith(".jsonl") && !lower.endsWith(".json")) {
9440
9640
  throw new Error("Labeling source file must be .jsonl or .csv");
9441
9641
  }
9442
- const lines = readFileSync9(file, "utf8").split(/\r?\n/);
9642
+ const lines = readFileSync10(file, "utf8").split(/\r?\n/);
9443
9643
  const errors = [];
9444
9644
  let rowCount = 0;
9445
9645
  for (const [index, rawLine] of lines.entries()) {
@@ -9562,7 +9762,7 @@ function registerLabelCommands(parent) {
9562
9762
  const { format } = validateUnlabeledFile(file, cmdOpts.inputColumn);
9563
9763
  const specId = await resolveSpecId(cmdOpts.spec, opts);
9564
9764
  const name = cmdOpts.name || file.split("/").pop().replace(/\.(jsonl|json|csv)$/, "");
9565
- const fileBytes = readFileSync9(file);
9765
+ const fileBytes = readFileSync10(file);
9566
9766
  const contentType = format === "csv" ? "text/csv" : "application/jsonl";
9567
9767
  const { data: uploadUrl } = await post(
9568
9768
  "/labeling-jobs/upload-url",
@@ -9735,7 +9935,7 @@ function registerLabelCommands(parent) {
9735
9935
  printError("Provide --output <text> or --file <path>.");
9736
9936
  process.exit(1);
9737
9937
  }
9738
- const output = cmdOpts.output ?? readFileSync9(cmdOpts.file, "utf8");
9938
+ const output = cmdOpts.output ?? readFileSync10(cmdOpts.file, "utf8");
9739
9939
  const jobId = await resolveLabelingJobId(id, opts);
9740
9940
  const indexes = parseIndexList(cmdOpts.row);
9741
9941
  const rowIds = await resolveRowIds(jobId, indexes, opts);
@@ -10225,7 +10425,7 @@ import {
10225
10425
  statSync as statSync4,
10226
10426
  createWriteStream,
10227
10427
  unlinkSync,
10228
- readFileSync as readFileSync11,
10428
+ readFileSync as readFileSync12,
10229
10429
  writeFileSync as writeFileSync5,
10230
10430
  readdirSync,
10231
10431
  rmSync
@@ -10236,7 +10436,7 @@ import { Readable, Transform } from "stream";
10236
10436
  import { pipeline } from "stream/promises";
10237
10437
 
10238
10438
  // src/commands/init.ts
10239
- import { existsSync as existsSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
10439
+ import { existsSync as existsSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
10240
10440
  import { resolve as resolve10 } from "path";
10241
10441
  var DEFAULT_SPEC_FILE2 = "tunedtensor.json";
10242
10442
  function loadSpec(filePath) {
@@ -10248,7 +10448,7 @@ Run \`tt init\` to create one.`
10248
10448
  );
10249
10449
  process.exit(1);
10250
10450
  }
10251
- const raw = JSON.parse(readFileSync10(resolved, "utf-8"));
10451
+ const raw = JSON.parse(readFileSync11(resolved, "utf-8"));
10252
10452
  return raw;
10253
10453
  }
10254
10454
 
@@ -11462,7 +11662,7 @@ function findSpecPath(explicitPath, modelPath) {
11462
11662
  return null;
11463
11663
  }
11464
11664
  function loadSystemPromptFromSpec(specPath) {
11465
- const spec = JSON.parse(readFileSync11(specPath, "utf8"));
11665
+ const spec = JSON.parse(readFileSync12(specPath, "utf8"));
11466
11666
  return buildSystemPrompt(spec);
11467
11667
  }
11468
11668
  function loadJsonSchemaForServe(schemaPath) {
@@ -11471,7 +11671,7 @@ function loadJsonSchemaForServe(schemaPath) {
11471
11671
  throw new Error(`JSON schema file not found: ${schemaPath}`);
11472
11672
  }
11473
11673
  try {
11474
- const schema = JSON.parse(readFileSync11(resolved, "utf8"));
11674
+ const schema = JSON.parse(readFileSync12(resolved, "utf8"));
11475
11675
  return JSON.stringify(schema);
11476
11676
  } catch (err) {
11477
11677
  throw new Error(`JSON schema file is not valid JSON: ${schemaPath}`);
@@ -12099,10 +12299,10 @@ function registerModelsCommands(parent) {
12099
12299
  }
12100
12300
 
12101
12301
  // src/commands/push.ts
12102
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
12302
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
12103
12303
  import { resolve as resolve12 } from "path";
12104
12304
  function persistRemoteId(filePath, id) {
12105
- const raw = JSON.parse(readFileSync12(filePath, "utf-8"));
12305
+ const raw = JSON.parse(readFileSync13(filePath, "utf-8"));
12106
12306
  raw.id = id;
12107
12307
  writeFileSync6(filePath, JSON.stringify(raw, null, 2) + "\n");
12108
12308
  }
@@ -12630,7 +12830,7 @@ async function runCli(version, runtime = {}) {
12630
12830
  }
12631
12831
 
12632
12832
  // src/index.ts
12633
- runCli("0.21.0").catch((err) => {
12833
+ runCli("0.22.0").catch((err) => {
12634
12834
  reportError(err);
12635
12835
  process.exit(1);
12636
12836
  });