@tangle-network/traces 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  appendAll,
7
7
  buildPolicyEvidenceRecord,
8
8
  buildSessionIndexFromRows,
9
+ cmdReplayVerify,
9
10
  collectSessionSelection,
10
11
  commandAnalyzer,
11
12
  commandRedactor,
@@ -26,8 +27,10 @@ import {
26
27
  parseSession,
27
28
  parseSince,
28
29
  planUpload,
30
+ readOtlpInput,
29
31
  readSessionIndexFile,
30
32
  renderInspectionReport,
33
+ renderValidation,
31
34
  resolveAdapter,
32
35
  resolveRunWatchTarget,
33
36
  runTraceImprovement,
@@ -42,6 +45,7 @@ import {
42
45
  streamSessions,
43
46
  totalAgenticFailureMessage,
44
47
  traceStreamEventsFromSpans,
48
+ validationExitCode,
45
49
  watchRunTarget,
46
50
  watchSessions,
47
51
  writeInspectionReportFile,
@@ -49,7 +53,7 @@ import {
49
53
  writePolicyEvidenceFile,
50
54
  writeSessionIndexFile,
51
55
  writeTraceEvidenceExportFile
52
- } from "./chunk-JCI5YXNT.js";
56
+ } from "./chunk-UY3M7XUF.js";
53
57
 
54
58
  // src/cli.ts
55
59
  import { readFileSync } from "fs";
@@ -155,6 +159,9 @@ function parseArgs(argv) {
155
159
  case "--otlp":
156
160
  a.otlp = next();
157
161
  break;
162
+ case "--otlp-out":
163
+ a.otlpOut = next();
164
+ break;
158
165
  case "--llm":
159
166
  a.llm = true;
160
167
  break;
@@ -239,6 +246,30 @@ function parseArgs(argv) {
239
246
  }
240
247
  return a;
241
248
  }
249
+ var OTLP_INPUT_COMMANDS = /* @__PURE__ */ new Set(["analyze", "investigate", "improve", "stream", "validate"]);
250
+ function migrateWritingOtlpFlag(args) {
251
+ if (args.otlp === void 0 || OTLP_INPUT_COMMANDS.has(args.command)) return args;
252
+ if (args.otlpOut !== void 0) {
253
+ throw new Error(
254
+ `${args.command} was given both --otlp and --otlp-out. --otlp is the deprecated spelling of --otlp-out on writing commands; pass only --otlp-out.`
255
+ );
256
+ }
257
+ console.error(
258
+ `warning: --otlp is deprecated on \`${args.command}\` and will be removed in 0.12. --otlp now READS OTLP-JSONL; use --otlp-out <path> to write the artifact. Treating it as --otlp-out for this run.`
259
+ );
260
+ return { ...args, otlpOut: args.otlp, otlp: void 0 };
261
+ }
262
+ function validateOtlpSelection(raw) {
263
+ const args = migrateWritingOtlpFlag(raw);
264
+ if (args.otlp === void 0) return args;
265
+ if (args.input) throw new Error("--otlp cannot be combined with an input file");
266
+ if (args.session) throw new Error("--otlp reads a file and cannot be combined with --session");
267
+ if (args.supervisorRunDir) throw new Error("--otlp cannot be combined with --supervisor-run-dir");
268
+ if (args.workflow || args.latestTurn) {
269
+ throw new Error("--workflow and --latest-turn expand harness sessions and cannot be combined with --otlp");
270
+ }
271
+ return args;
272
+ }
242
273
  var CURRENT_SESSION_COMMANDS = /* @__PURE__ */ new Set([
243
274
  "analyze",
244
275
  "investigate",
@@ -457,7 +488,45 @@ function selectedSessionSource(ref, spans, sessionIdOverride) {
457
488
  } : {}
458
489
  };
459
490
  }
491
+ async function collectOtlpSpans(path) {
492
+ const input = await readOtlpInput(path);
493
+ if (input.spans.length === 0) {
494
+ throw new Error(
495
+ `no analyzable spans in ${path} (${input.files.length} file(s), ${input.rows.length} row(s), ${input.issues.length} unusable). Run \`traces validate ${path}\` for the per-finding reason.`
496
+ );
497
+ }
498
+ const fileByTrace = /* @__PURE__ */ new Map();
499
+ for (const file of input.files) {
500
+ for (const traceId of file.traceIds) if (!fileByTrace.has(traceId)) fileByTrace.set(traceId, file.path);
501
+ }
502
+ const byTrace = /* @__PURE__ */ new Map();
503
+ for (const span of input.spans) {
504
+ const grouped = byTrace.get(span.trace_id) ?? [];
505
+ grouped.push(span);
506
+ byTrace.set(span.trace_id, grouped);
507
+ }
508
+ return {
509
+ spans: [...input.spans],
510
+ harness: "otlp",
511
+ cwds: [],
512
+ sources: [...byTrace].map(([traceId, spans]) => selectedSessionSource({
513
+ harness: "otlp",
514
+ sessionId: traceId,
515
+ path: fileByTrace.get(traceId) ?? path,
516
+ cwd: null,
517
+ mtimeMs: 0
518
+ }, spans, traceId)),
519
+ conformance: input.validation,
520
+ conformanceSubject: path,
521
+ conformanceFiles: input.files,
522
+ conformanceSkipped: input.skipped,
523
+ conformanceIssues: input.issues,
524
+ conformanceWithheld: input.withheld,
525
+ conformanceUnreadable: input.unreadable
526
+ };
527
+ }
460
528
  async function collectSpans(args) {
529
+ if (args.otlp) return collectOtlpSpans(args.otlp);
461
530
  if (args.input) {
462
531
  if (args.workflow) throw new Error("--workflow reads discovered sessions and cannot be combined with an input file");
463
532
  return collectImportedSpans(args);
@@ -481,11 +550,37 @@ async function collectSpans(args) {
481
550
  ...selection.workflow ? { workflow: selection.workflow } : {}
482
551
  };
483
552
  }
553
+ async function cmdValidate(args) {
554
+ const path = args.otlp ?? args.input;
555
+ if (!path) {
556
+ throw new Error("validate needs an OTLP-JSONL file or directory: `traces validate <file|dir>`");
557
+ }
558
+ const input = await readOtlpInput(path);
559
+ const report = renderValidation(input.validation, {
560
+ subject: path,
561
+ files: input.files,
562
+ skipped: input.skipped,
563
+ issues: input.issues,
564
+ withheld: input.withheld,
565
+ unreadable: input.unreadable,
566
+ spans: input.spans
567
+ });
568
+ const validation = input.validation;
569
+ if (args.out) {
570
+ await saveReport(args.out, report);
571
+ console.log(
572
+ `conformance report \u2192 ${args.out} (${validation.findings.length} finding(s), ${validation.capabilities.filter((capability) => capability.available).length}/${validation.capabilities.length} capabilities available)`
573
+ );
574
+ } else {
575
+ console.log(report);
576
+ }
577
+ process.exitCode = validationExitCode(validation);
578
+ }
484
579
  async function cmdConvert(args) {
485
580
  const { spans, workflow } = await collectSpans(args);
486
581
  if (spans.length === 0) throw new Error("no spans found for the given selection");
487
582
  warnIncompleteWorkflow(workflow);
488
- const path = await writeOtlpFile(spans, args.otlp);
583
+ const path = await writeOtlpFile(spans, args.otlpOut);
489
584
  console.log(`wrote ${spans.length} spans \u2192 ${path}`);
490
585
  }
491
586
  function workflowIssueText(issue) {
@@ -519,7 +614,7 @@ async function cmdEvidence(args) {
519
614
  const rows = selection.rows.filter((row) => row.spans.length > 0);
520
615
  if (rows.length === 0) throw new Error("no spans found for the given selection");
521
616
  warnIncompleteWorkflow(selection.workflow);
522
- const otlpPath = args.otlp ? await writeOtlpFile(rows.flatMap((row) => row.spans), args.otlp) : void 0;
617
+ const otlpPath = args.otlpOut ? await writeOtlpFile(rows.flatMap((row) => row.spans), args.otlpOut) : void 0;
523
618
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
524
619
  const records = await Promise.all(rows.map(
525
620
  (row) => buildPolicyEvidenceRecord(row.ref, row.spans, {
@@ -578,7 +673,7 @@ async function cmdExport(args) {
578
673
  if (!args.input) throw new Error("export needs an input file; run `traces export --help` for examples");
579
674
  const format = traceEvidenceFormat(args.format);
580
675
  const attributes = await loadExportAttributes(args);
581
- const outPath = args.out ?? args.otlp;
676
+ const outPath = args.out ?? args.otlpOut;
582
677
  if (outPath) {
583
678
  const result2 = await writeTraceEvidenceExportFile(args.input, outPath, { format, attributes });
584
679
  console.log(
@@ -780,8 +875,20 @@ async function cmdInvestigate(args) {
780
875
  }
781
876
  assertAgenticAnalystsRan(args, result.agenticPerAnalyst);
782
877
  }
878
+ function conformanceOptions(collected) {
879
+ return {
880
+ ...collected.conformance ? { conformance: collected.conformance } : {},
881
+ ...collected.conformanceSubject ? { conformanceSubject: collected.conformanceSubject } : {},
882
+ ...collected.conformanceFiles ? { conformanceFiles: collected.conformanceFiles } : {},
883
+ ...collected.conformanceSkipped ? { conformanceSkipped: collected.conformanceSkipped } : {},
884
+ ...collected.conformanceIssues ? { conformanceIssues: collected.conformanceIssues } : {},
885
+ ...collected.conformanceWithheld ? { conformanceWithheld: collected.conformanceWithheld } : {},
886
+ ...collected.conformanceUnreadable ? { conformanceUnreadable: collected.conformanceUnreadable } : {}
887
+ };
888
+ }
783
889
  async function investigate(args, options = {}) {
784
- const { spans, harness, cwds, sources, workflow } = await collectSpans(args);
890
+ const collected = await collectSpans(args);
891
+ const { spans, harness, cwds, sources, workflow } = collected;
785
892
  if (spans.length === 0) throw new Error("no spans found for the given selection");
786
893
  const config = args.config !== void 0 || options.loadDefaultConfig !== false ? await loadTracesConfig(args.config) : void 0;
787
894
  const analystModel = args.model ?? process.env.TRACES_ANALYST_MODEL ?? DEFAULT_ANALYST_MODEL;
@@ -796,14 +903,16 @@ async function investigate(args, options = {}) {
796
903
  engine,
797
904
  model: args.llm ? analystModel : args.model,
798
905
  budgetUsd: args.budget,
799
- otlpOutPath: args.otlp,
906
+ otlpOutPath: args.otlpOut,
800
907
  externalAnalyzers: externalAnalyzersFromArgs(args),
801
908
  analyzerPrompt: args.analyzerPrompt,
802
- log: analystLog
909
+ log: analystLog,
910
+ ...conformanceOptions(collected)
803
911
  }, config));
804
912
  }
805
913
  async function cmdImprove(args) {
806
- const { spans, harness, cwds, sources, workflow } = await collectSpans(args);
914
+ const collected = await collectSpans(args);
915
+ const { spans, harness, cwds, sources, workflow } = collected;
807
916
  if (spans.length === 0) throw new Error("no spans found for the given selection");
808
917
  const config = await loadTracesConfig(args.config);
809
918
  const analystModel = args.model ?? process.env.TRACES_ANALYST_MODEL ?? DEFAULT_ANALYST_MODEL;
@@ -819,10 +928,11 @@ async function cmdImprove(args) {
819
928
  engine,
820
929
  model: args.llm ? analystModel : args.model,
821
930
  budgetUsd: args.budget,
822
- otlpOutPath: args.otlp,
931
+ otlpOutPath: args.otlpOut,
823
932
  externalAnalyzers: externalAnalyzersFromArgs(args),
824
933
  analyzerPrompt: args.analyzerPrompt,
825
- log: analystLog
934
+ log: analystLog,
935
+ ...conformanceOptions(collected)
826
936
  }, config),
827
937
  outDir: args.dir ?? args.out
828
938
  });
@@ -890,12 +1000,13 @@ async function streamExplicitSession(args, extraAnalysts) {
890
1000
  async function cmdStream(args) {
891
1001
  const config = await loadTracesConfig(args.config);
892
1002
  const preset = streamPreset(args);
893
- if (args.input) {
894
- const { spans, harness } = await collectImportedSpans(args);
1003
+ const source = args.otlp ?? args.input;
1004
+ if (source) {
1005
+ const { spans, harness } = args.otlp ? await collectOtlpSpans(args.otlp) : await collectImportedSpans(args);
895
1006
  const ref = {
896
1007
  harness,
897
- sessionId: basename(args.input),
898
- path: resolve(args.input),
1008
+ sessionId: basename(source),
1009
+ path: resolve(source),
899
1010
  cwd: args.cwd ?? null,
900
1011
  mtimeMs: Date.now()
901
1012
  };
@@ -1034,7 +1145,7 @@ Window: since ${new Date(sinceMs).toISOString()}
1034
1145
  return;
1035
1146
  }
1036
1147
  if (args.dryRun) {
1037
- const res2 = await executeUpload(plan, { dryRun: true, otlpOut: args.otlp, stripContent: args.noContent, redactor });
1148
+ const res2 = await executeUpload(plan, { dryRun: true, otlpOut: args.otlpOut, stripContent: args.noContent, redactor });
1038
1149
  console.log(`dry run: ${candidates.length - res2.skippedSessions} session(s), ${totalRedactions} redaction(s). Redacted OTLP -> ${res2.otlpPath}`);
1039
1150
  console.log("No upload performed. Set TANGLE_INGEST_URL / TANGLE_INGEST_API_KEY / TANGLE_TENANT_ID and drop --dry-run to send.");
1040
1151
  return;
@@ -1097,21 +1208,71 @@ The command validates every native step, rejects annotation leakage, writes
1097
1208
  <traj_id>.otlp.jsonl files, and records input and output SHA-256 hashes in
1098
1209
  codetracebench-import.json. The output directory must not already exist.`);
1099
1210
  }
1211
+ function usageValidate() {
1212
+ console.log(`traces validate: report what a trace can and cannot answer
1213
+
1214
+ Usage:
1215
+ traces validate <file.jsonl|dir> [--out conformance.md]
1216
+
1217
+ Reads OTLP-JSONL and prints the conformance report from
1218
+ @tangle-network/agent-trace-contract:
1219
+
1220
+ - findings by severity, each naming the CONSEQUENCE of the defect and the
1221
+ capabilities it blocks
1222
+ - the capability table: token-accounting, cost-attribution, tool-usage,
1223
+ loop-convergence, tree-comparison, steering-chain, latency-analysis \u2014
1224
+ available or not, with the trace's own reason
1225
+ - rows that could not be read at all, with the line and the reason
1226
+
1227
+ A directory reads the OTLP files under it, recursively \u2014 only the otlp/
1228
+ subdirectory when the producer made one. A run directory's raw event, stream and
1229
+ SDK logs are also *.jsonl; they are listed with what they actually hold, not read
1230
+ as several hundred broken spans. Identical re-declarations of a span across
1231
+ per-shot files are counted as repeats, not defects; only a repeat with DIFFERENT
1232
+ content is an error.
1233
+
1234
+ Exit code:
1235
+ 0 no error findings, and at least one capability available (warnings mean
1236
+ thinner analysis, not wrong analysis)
1237
+ 1 an error finding, or nothing analysable at all \u2014 an empty file, or one
1238
+ whose every line was unreadable, answers no question and is not a pass
1239
+
1240
+ Nothing is fixed up: a span with an unreadable timestamp is reported, never
1241
+ repaired, because a synthesized value puts invented work into a total.
1242
+
1243
+ Examples:
1244
+ traces validate spans.otlp.jsonl
1245
+ traces validate results/sessions --out conformance.md
1246
+ traces validate spans.otlp.jsonl && traces analyze --otlp spans.otlp.jsonl`);
1247
+ }
1100
1248
  function usage() {
1101
- console.log(`traces: analyze, observe & upload coding-agent sessions
1249
+ console.log(`traces: analyze agent traces \u2014 yours, or any system's
1250
+
1251
+ Two ways in:
1252
+ --otlp <file|dir> Spans a system already emitted in the
1253
+ @tangle-network/agent-trace-contract shape. No adapter, no
1254
+ translation. This is how you integrate a NEW system.
1255
+ --harness <id> Run an adapter over a coding agent's proprietary session
1256
+ store (Claude Code, Codex, OpenCode, \u2026) \u2014 the legacy edge,
1257
+ for formats we do not control.
1102
1258
 
1103
1259
  Commands:
1260
+ validate Report what a trace can and cannot answer (exit 1 on error findings)
1104
1261
  list List discovered sessions
1105
- analyze Run analyst suite + loop/waste pipelines over sessions or an input file
1262
+ analyze Run analyst suite + loop/waste pipelines over OTLP (--otlp), sessions,
1263
+ or an input file; names every analysis the spans could not support
1106
1264
  (--supervisor-run-dir <dir> reports a supervision tree instead)
1107
1265
  investigate Run typed investigation flow, including BYO config + evidence-backed actions
1108
1266
  improve Write findings, evidence, report, and canonical trace artifacts
1109
- convert Emit OTLP-JSONL only (HALO: use analyze --analyzer halo)
1267
+ convert Emit OTLP-JSONL only, to --otlp-out (HALO: use analyze --analyzer halo)
1110
1268
  index Emit a reusable session index JSON for later investigation
1111
1269
  inspect Read a session index and print ranked improvement findings
1112
1270
  export Convert evidence/events files to OpenInference JSONL for HALO
1113
1271
  import-codetracebench
1114
1272
  Convert CodeTracer-normalized CodeTraceBench trajectories in bulk
1273
+ replay-verify
1274
+ Replay a trajectory prefix in a sandbox, execute step k both recorded
1275
+ and corrected, and emit an executed-proof verdict (--help for flags)
1115
1276
  evidence Emit compact session-evidence JSONL for downstream policy miners
1116
1277
  stream Emit JSONL trace stream events for live visualizers or replay
1117
1278
  watch Online observer: tail active sessions, notify on loops + semantic findings
@@ -1138,7 +1299,15 @@ Options:
1138
1299
  --since <t> upload: window, 30m / 2h / 7d or an ISO date (default 24h); analyze: ISO cutoff
1139
1300
  --out <path> Write report to a file
1140
1301
  --dir <path> improve: write artifacts to this directory
1141
- --otlp <path> OTLP artifact path (also evidence provenance / dry-run upload preview)
1302
+ --otlp <file|dir> READ OTLP-JSONL emitted by any system, skipping the adapters.
1303
+ A directory reads the OTLP files under it \u2014 only the otlp/
1304
+ subdirectory when the producer made one \u2014 and names the JSONL
1305
+ that is not OTLP instead of reading it as broken spans.
1306
+ Supported by: validate, analyze, investigate, improve, stream.
1307
+ On a WRITING command it is the deprecated spelling of
1308
+ --otlp-out; it still works, with a warning, until 0.12.
1309
+ --otlp-out <path> WRITE the OTLP-JSONL artifact here (also evidence
1310
+ provenance / dry-run upload preview)
1142
1311
  --format <kind> analyze/export: auto | policy-evidence | sandbox-events | openinference | intelligence-spans | chat-trajectory
1143
1312
  --metadata <json> analyze/export file: attach JSON object fields as span attributes
1144
1313
  --attr <k=v> analyze/export file: attach one span attribute (repeatable)
@@ -1166,7 +1335,8 @@ Options:
1166
1335
  --redactor <cmd> upload: external PII scrubber (JSON array stdin\u2192stdout) after the regex pass
1167
1336
  --yes, -y upload: skip the confirmation prompt
1168
1337
  --version, -v Print the installed traces version
1169
- --help, -h Show help (use \`traces export --help\` or \`traces import-codetracebench --help\`)
1338
+ --help, -h Show help (use \`traces validate --help\`, \`traces export --help\`,
1339
+ or \`traces import-codetracebench --help\`)
1170
1340
 
1171
1341
  Watch a run tree (any depth; driver spend and child spend stay separate,
1172
1342
  and a cost the harness never priced reads "unknown", never $0):
@@ -1183,18 +1353,24 @@ async function main() {
1183
1353
  console.log(`traces ${packageVersion()}`);
1184
1354
  return;
1185
1355
  }
1356
+ if (rawArgs[0] === "replay-verify") {
1357
+ await cmdReplayVerify(rawArgs.slice(1));
1358
+ return;
1359
+ }
1186
1360
  const parsedArgs = parseArgs(rawArgs);
1187
1361
  if (parsedArgs.help) {
1188
1362
  if (parsedArgs.command === "import-codetracebench" || parsedArgs.command === "help" && parsedArgs.input === "import-codetracebench") usageImportCodeTraceBench();
1189
1363
  else if (parsedArgs.command === "export" || parsedArgs.command === "help" && parsedArgs.input === "export") usageExport();
1364
+ else if (parsedArgs.command === "validate" || parsedArgs.command === "help" && parsedArgs.input === "validate") usageValidate();
1190
1365
  else usage();
1191
1366
  return;
1192
1367
  }
1193
- const args = validateWorkflowSelection(applyCurrentSessionSelection(parsedArgs));
1368
+ const args = validateOtlpSelection(validateWorkflowSelection(applyCurrentSessionSelection(parsedArgs)));
1194
1369
  switch (args.command) {
1195
1370
  case "help":
1196
1371
  if (args.input === "import-codetracebench") usageImportCodeTraceBench();
1197
1372
  else if (args.input === "export") usageExport();
1373
+ else if (args.input === "validate") usageValidate();
1198
1374
  else usage();
1199
1375
  break;
1200
1376
  case "list":
@@ -1203,6 +1379,9 @@ async function main() {
1203
1379
  case "analyze":
1204
1380
  await cmdAnalyze(args);
1205
1381
  break;
1382
+ case "validate":
1383
+ await cmdValidate(args);
1384
+ break;
1206
1385
  case "investigate":
1207
1386
  await cmdInvestigate(args);
1208
1387
  break;