@tangle-network/traces 0.11.0 → 0.11.3

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