@tangle-network/traces 0.15.4 → 0.16.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.
@@ -881,8 +881,8 @@ function traceContractBuildId(fromFile = import.meta.url) {
881
881
  }
882
882
  var buildIdCache = /* @__PURE__ */ new Map();
883
883
  function traceContractBuildIdOrNull(fromFile = import.meta.url) {
884
- const cached = buildIdCache.get(fromFile);
885
- if (cached !== void 0) return cached;
884
+ const cached2 = buildIdCache.get(fromFile);
885
+ if (cached2 !== void 0) return cached2;
886
886
  let resolved;
887
887
  try {
888
888
  resolved = traceContractBuildId(fromFile);
@@ -1018,8 +1018,8 @@ function serializeSpans(spans) {
1018
1018
  async function writeOtlpFile(spans, outPath) {
1019
1019
  const { mkdtemp: mkdtemp8, writeFile: writeFile13 } = await import("fs/promises");
1020
1020
  const { tmpdir: tmpdir8 } = await import("os");
1021
- const { join: join34 } = await import("path");
1022
- const path = outPath ?? join34(await mkdtemp8(join34(tmpdir8(), "traces-")), "spans.otlp.jsonl");
1021
+ const { join: join35 } = await import("path");
1022
+ const path = outPath ?? join35(await mkdtemp8(join35(tmpdir8(), "traces-")), "spans.otlp.jsonl");
1023
1023
  await writeFile13(path, serializeSpans(spans), "utf8");
1024
1024
  return path;
1025
1025
  }
@@ -3844,9 +3844,9 @@ function itemCountsJson(counts) {
3844
3844
  }
3845
3845
  function placeInnerSpans(pending, windows) {
3846
3846
  return pending.map((entry) => {
3847
- const { parent, join: join34 } = joinItem(windows, entry.startMs, entry.endMs);
3847
+ const { parent, join: join35 } = joinItem(windows, entry.startMs, entry.endMs);
3848
3848
  if (parent) entry.span.parent_span_id = parent.span_id;
3849
- entry.span.attributes["traces.codex.item_join"] = join34;
3849
+ entry.span.attributes["traces.codex.item_join"] = join35;
3850
3850
  return entry.span;
3851
3851
  });
3852
3852
  }
@@ -4170,8 +4170,8 @@ var CodexAdapter = class {
4170
4170
  const completedItemKeys = /* @__PURE__ */ new Set();
4171
4171
  const unmodeledItemCounts = /* @__PURE__ */ new Map();
4172
4172
  const droppedItemCounts = /* @__PURE__ */ new Map();
4173
- const countItem = (counts, label) => {
4174
- counts.set(label, (counts.get(label) ?? 0) + 1);
4173
+ const countItem = (counts, label2) => {
4174
+ counts.set(label2, (counts.get(label2) ?? 0) + 1);
4175
4175
  };
4176
4176
  let taskIndex = 0;
4177
4177
  const unpairedUserItems = [];
@@ -4681,11 +4681,11 @@ var CodexAdapter = class {
4681
4681
  const input = reportedCount(sessionTotalUsage.input_tokens);
4682
4682
  const output = reportedCount(sessionTotalUsage.output_tokens);
4683
4683
  const reasoning = reportedCount(sessionTotalUsage.reasoning_output_tokens);
4684
- const cached = reportedCount(sessionTotalUsage.cached_input_tokens);
4684
+ const cached2 = reportedCount(sessionTotalUsage.cached_input_tokens);
4685
4685
  if (input !== void 0) root.attributes[SESSION_TOTAL_INPUT_TOKENS] = input;
4686
4686
  if (output !== void 0) root.attributes[SESSION_TOTAL_OUTPUT_TOKENS] = output;
4687
4687
  if (reasoning !== void 0) root.attributes[SESSION_TOTAL_REASONING_TOKENS] = reasoning;
4688
- if (cached !== void 0) root.attributes[SESSION_TOTAL_CACHED_INPUT_TOKENS] = cached;
4688
+ if (cached2 !== void 0) root.attributes[SESSION_TOTAL_CACHED_INPUT_TOKENS] = cached2;
4689
4689
  }
4690
4690
  }
4691
4691
  if (inheritedSpansEmitted > 0) root.attributes[INHERITED_SPAN_COUNT_ATTR] = inheritedSpansEmitted;
@@ -4759,14 +4759,14 @@ function earlier(left, right) {
4759
4759
  function later(left, right) {
4760
4760
  return Date.parse(left) >= Date.parse(right) ? left : right;
4761
4761
  }
4762
- function requireObject2(value, sourcePath, label) {
4762
+ function requireObject2(value, sourcePath, label2) {
4763
4763
  const object = objectValue(value);
4764
- if (!object) throw new CodexExecStreamError(sourcePath, `${label} must be an object`);
4764
+ if (!object) throw new CodexExecStreamError(sourcePath, `${label2} must be an object`);
4765
4765
  return object;
4766
4766
  }
4767
- function requireString(value, sourcePath, label) {
4767
+ function requireString(value, sourcePath, label2) {
4768
4768
  const string = stringValue(value);
4769
- if (!string) throw new CodexExecStreamError(sourcePath, `${label} must be a non-empty string`);
4769
+ if (!string) throw new CodexExecStreamError(sourcePath, `${label2} must be a non-empty string`);
4770
4770
  return string;
4771
4771
  }
4772
4772
  function optionalTokenCount(usage, key, sourcePath) {
@@ -6719,7 +6719,7 @@ function extractAbsolutePaths(spans) {
6719
6719
  async function resolveRepoAttrs(cwd) {
6720
6720
  if (!cwd) return {};
6721
6721
  const attrs = { [ATTR.CWD]: cwd };
6722
- const basename18 = (() => {
6722
+ const basename19 = (() => {
6723
6723
  const trimmed = cwd.replace(/\/+$/g, "");
6724
6724
  const idx = trimmed.lastIndexOf("/");
6725
6725
  const name = idx === -1 ? trimmed : trimmed.slice(idx + 1);
@@ -6741,7 +6741,7 @@ async function resolveRepoAttrs(cwd) {
6741
6741
  if (commit) attrs[ATTR.GIT_COMMIT] = commit;
6742
6742
  } catch {
6743
6743
  }
6744
- attrs[ATTR.SUBJECT_KEY] = basename18;
6744
+ attrs[ATTR.SUBJECT_KEY] = basename19;
6745
6745
  return attrs;
6746
6746
  }
6747
6747
  async function repoCandidateForPath(path) {
@@ -6797,8 +6797,8 @@ function replaceRepoAttrs(spans, attrs) {
6797
6797
  }
6798
6798
  }
6799
6799
  async function resolutionForWorkdirEvidence(evidence3, cache) {
6800
- const cached = cache.get(evidence3.path);
6801
- if (cached !== void 0) return cached;
6800
+ const cached2 = cache.get(evidence3.path);
6801
+ if (cached2 !== void 0) return cached2;
6802
6802
  const cwd = await repoCandidateForPath(evidence3.path);
6803
6803
  if (!cwd) {
6804
6804
  cache.set(evidence3.path, null);
@@ -6902,9 +6902,9 @@ async function locateSessions(adapter, opts = {}) {
6902
6902
  for (const ref of refs) {
6903
6903
  let matched = cwdMatchesSelection(ref.cwd, cwdSelections);
6904
6904
  if (!matched && ref.cwd !== null) {
6905
- const cached = resolvedMatch.get(ref.cwd);
6906
- matched = cached ?? await cwdMatchesSelectionResolved(ref.cwd, cwdSelections);
6907
- if (cached === void 0) resolvedMatch.set(ref.cwd, matched);
6905
+ const cached2 = resolvedMatch.get(ref.cwd);
6906
+ matched = cached2 ?? await cwdMatchesSelectionResolved(ref.cwd, cwdSelections);
6907
+ if (cached2 === void 0) resolvedMatch.set(ref.cwd, matched);
6908
6908
  }
6909
6909
  if (!matched && !(ref.cwd === null && ref.integrity)) continue;
6910
6910
  byKey.set(refKey(ref), ref);
@@ -7534,106 +7534,106 @@ var RESULT_FAILURE_KEYS = /* @__PURE__ */ new Set(["analyzer", "kind", "ok", "ou
7534
7534
  var RESULT_REPORT_KEYS = /* @__PURE__ */ new Set(["analyzer", "kind", "ok", "output"]);
7535
7535
  var RESULT_FINDING_KEYS = /* @__PURE__ */ new Set(["analyzer", "kind", "ok", "output", "findings"]);
7536
7536
  var RESULT_DISCOVERY_KEYS = /* @__PURE__ */ new Set(["analyzer", "kind", "ok", "output", "candidates"]);
7537
- function record(value, label) {
7537
+ function record(value, label2) {
7538
7538
  if (!value || typeof value !== "object" || Array.isArray(value)) {
7539
- throw new TypeError(`${label} must be an object`);
7539
+ throw new TypeError(`${label2} must be an object`);
7540
7540
  }
7541
7541
  return value;
7542
7542
  }
7543
- function assertOnlyKeys(value, allowed, label) {
7543
+ function assertOnlyKeys(value, allowed, label2) {
7544
7544
  for (const key of Object.keys(value)) {
7545
- if (!allowed.has(key)) throw new TypeError(`${label} contains unexpected field '${key}'`);
7545
+ if (!allowed.has(key)) throw new TypeError(`${label2} contains unexpected field '${key}'`);
7546
7546
  }
7547
7547
  }
7548
- function text(value, label) {
7549
- if (typeof value !== "string") throw new TypeError(`${label} must be a string`);
7548
+ function text(value, label2) {
7549
+ if (typeof value !== "string") throw new TypeError(`${label2} must be a string`);
7550
7550
  return value;
7551
7551
  }
7552
- function nonEmptyString3(value, label) {
7553
- const decoded = text(value, label);
7554
- if (decoded.length === 0) throw new TypeError(`${label} must be a non-empty string`);
7552
+ function nonEmptyString3(value, label2) {
7553
+ const decoded = text(value, label2);
7554
+ if (decoded.length === 0) throw new TypeError(`${label2} must be a non-empty string`);
7555
7555
  return decoded;
7556
7556
  }
7557
- function optionalString(value, label) {
7558
- return value === void 0 ? void 0 : text(value, label);
7557
+ function optionalString(value, label2) {
7558
+ return value === void 0 ? void 0 : text(value, label2);
7559
7559
  }
7560
- function boolean(value, label) {
7561
- if (typeof value !== "boolean") throw new TypeError(`${label} must be a boolean`);
7560
+ function boolean(value, label2) {
7561
+ if (typeof value !== "boolean") throw new TypeError(`${label2} must be a boolean`);
7562
7562
  return value;
7563
7563
  }
7564
- function integer(value, label, minimum) {
7564
+ function integer(value, label2, minimum) {
7565
7565
  if (!Number.isSafeInteger(value) || value < minimum) {
7566
- throw new TypeError(`${label} must be a safe integer >= ${minimum}`);
7566
+ throw new TypeError(`${label2} must be a safe integer >= ${minimum}`);
7567
7567
  }
7568
7568
  return value;
7569
7569
  }
7570
- function optionalRecord(value, label) {
7571
- return value === void 0 ? void 0 : record(value, label);
7570
+ function optionalRecord(value, label2) {
7571
+ return value === void 0 ? void 0 : record(value, label2);
7572
7572
  }
7573
7573
  function spanEvidenceUri(traceId, spanId) {
7574
7574
  return `trace://${encodeURIComponent(traceId)}/span/${encodeURIComponent(spanId)}`;
7575
7575
  }
7576
- function validateSpanEvidence(uri, knownSpanUris, label) {
7576
+ function validateSpanEvidence(uri, knownSpanUris, label2) {
7577
7577
  if (knownSpanUris && !knownSpanUris.has(uri)) {
7578
- throw new TypeError(`${label} references unknown span evidence URI '${uri}'`);
7578
+ throw new TypeError(`${label2} references unknown span evidence URI '${uri}'`);
7579
7579
  }
7580
7580
  }
7581
- function decodeEvidenceRef(value, label, knownSpanUris) {
7582
- const ref = record(value, label);
7583
- assertOnlyKeys(ref, EVIDENCE_KEYS, label);
7584
- const kind = nonEmptyString3(ref.kind, `${label}.kind`);
7581
+ function decodeEvidenceRef(value, label2, knownSpanUris) {
7582
+ const ref = record(value, label2);
7583
+ assertOnlyKeys(ref, EVIDENCE_KEYS, label2);
7584
+ const kind = nonEmptyString3(ref.kind, `${label2}.kind`);
7585
7585
  if (!["span", "event", "artifact", "finding", "metric"].includes(kind)) {
7586
- throw new TypeError(`${label}.kind is unsupported`);
7586
+ throw new TypeError(`${label2}.kind is unsupported`);
7587
7587
  }
7588
- const uri = nonEmptyString3(ref.uri, `${label}.uri`);
7589
- if (kind === "span") validateSpanEvidence(uri, knownSpanUris, label);
7590
- const excerpt = optionalString(ref.excerpt, `${label}.excerpt`);
7588
+ const uri = nonEmptyString3(ref.uri, `${label2}.uri`);
7589
+ if (kind === "span") validateSpanEvidence(uri, knownSpanUris, label2);
7590
+ const excerpt = optionalString(ref.excerpt, `${label2}.excerpt`);
7591
7591
  return {
7592
7592
  kind,
7593
7593
  uri,
7594
7594
  ...excerpt === void 0 ? {} : { excerpt }
7595
7595
  };
7596
7596
  }
7597
- function decodeFinding(value, label, knownSpanUris) {
7598
- const finding2 = record(value, label);
7599
- assertOnlyKeys(finding2, FINDING_KEYS, label);
7597
+ function decodeFinding(value, label2, knownSpanUris) {
7598
+ const finding2 = record(value, label2);
7599
+ assertOnlyKeys(finding2, FINDING_KEYS, label2);
7600
7600
  if (finding2.schema_version !== "1.0.0") {
7601
- throw new TypeError(`${label}.schema_version must be '1.0.0'`);
7601
+ throw new TypeError(`${label2}.schema_version must be '1.0.0'`);
7602
7602
  }
7603
- const findingId = nonEmptyString3(finding2.finding_id, `${label}.finding_id`);
7604
- const analystId = nonEmptyString3(finding2.analyst_id, `${label}.analyst_id`);
7605
- const producedAt = nonEmptyString3(finding2.produced_at, `${label}.produced_at`);
7603
+ const findingId = nonEmptyString3(finding2.finding_id, `${label2}.finding_id`);
7604
+ const analystId = nonEmptyString3(finding2.analyst_id, `${label2}.analyst_id`);
7605
+ const producedAt = nonEmptyString3(finding2.produced_at, `${label2}.produced_at`);
7606
7606
  if (!Number.isFinite(Date.parse(producedAt))) {
7607
- throw new TypeError(`${label}.produced_at must be a valid timestamp`);
7607
+ throw new TypeError(`${label2}.produced_at must be a valid timestamp`);
7608
7608
  }
7609
- const severity = nonEmptyString3(finding2.severity, `${label}.severity`);
7609
+ const severity = nonEmptyString3(finding2.severity, `${label2}.severity`);
7610
7610
  if (!["critical", "high", "medium", "low", "info"].includes(severity)) {
7611
- throw new TypeError(`${label}.severity is unsupported`);
7611
+ throw new TypeError(`${label2}.severity is unsupported`);
7612
7612
  }
7613
7613
  if (!Array.isArray(finding2.evidence_refs)) {
7614
- throw new TypeError(`${label}.evidence_refs must be an array`);
7614
+ throw new TypeError(`${label2}.evidence_refs must be an array`);
7615
7615
  }
7616
7616
  if (typeof finding2.confidence !== "number" || !Number.isFinite(finding2.confidence) || finding2.confidence < 0 || finding2.confidence > 1) {
7617
- throw new TypeError(`${label}.confidence must be a finite number from 0 to 1`);
7617
+ throw new TypeError(`${label2}.confidence must be a finite number from 0 to 1`);
7618
7618
  }
7619
- const rationale = optionalString(finding2.rationale, `${label}.rationale`);
7619
+ const rationale = optionalString(finding2.rationale, `${label2}.rationale`);
7620
7620
  const recommendedAction = optionalString(
7621
7621
  finding2.recommended_action,
7622
- `${label}.recommended_action`
7622
+ `${label2}.recommended_action`
7623
7623
  );
7624
- const validationPlan = optionalString(finding2.validation_plan, `${label}.validation_plan`);
7625
- const subject = optionalString(finding2.subject, `${label}.subject`);
7626
- const derivedFromJudge = finding2.derived_from_judge === void 0 ? void 0 : boolean(finding2.derived_from_judge, `${label}.derived_from_judge`);
7627
- const metadata = optionalRecord(finding2.metadata, `${label}.metadata`);
7624
+ const validationPlan = optionalString(finding2.validation_plan, `${label2}.validation_plan`);
7625
+ const subject = optionalString(finding2.subject, `${label2}.subject`);
7626
+ const derivedFromJudge = finding2.derived_from_judge === void 0 ? void 0 : boolean(finding2.derived_from_judge, `${label2}.derived_from_judge`);
7627
+ const metadata = optionalRecord(finding2.metadata, `${label2}.metadata`);
7628
7628
  return {
7629
7629
  schema_version: "1.0.0",
7630
7630
  finding_id: findingId,
7631
7631
  analyst_id: analystId,
7632
7632
  produced_at: producedAt,
7633
7633
  severity,
7634
- area: nonEmptyString3(finding2.area, `${label}.area`),
7635
- claim: nonEmptyString3(finding2.claim, `${label}.claim`),
7636
- evidence_refs: finding2.evidence_refs.map((ref, index) => decodeEvidenceRef(ref, `${label}.evidence_refs[${index}]`, knownSpanUris)),
7634
+ area: nonEmptyString3(finding2.area, `${label2}.area`),
7635
+ claim: nonEmptyString3(finding2.claim, `${label2}.claim`),
7636
+ evidence_refs: finding2.evidence_refs.map((ref, index) => decodeEvidenceRef(ref, `${label2}.evidence_refs[${index}]`, knownSpanUris)),
7637
7637
  confidence: finding2.confidence,
7638
7638
  ...rationale === void 0 ? {} : { rationale },
7639
7639
  ...recommendedAction === void 0 ? {} : { recommended_action: recommendedAction },
@@ -7643,32 +7643,32 @@ function decodeFinding(value, label, knownSpanUris) {
7643
7643
  ...metadata === void 0 ? {} : { metadata }
7644
7644
  };
7645
7645
  }
7646
- function decodeCandidate(value, label, knownSpanUris) {
7647
- const candidate = record(value, label);
7648
- assertOnlyKeys(candidate, CANDIDATE_KEYS, label);
7646
+ function decodeCandidate(value, label2, knownSpanUris) {
7647
+ const candidate = record(value, label2);
7648
+ assertOnlyKeys(candidate, CANDIDATE_KEYS, label2);
7649
7649
  if (candidate.status !== "needs_review") {
7650
- throw new TypeError(`${label}.status must be 'needs_review'`);
7650
+ throw new TypeError(`${label2}.status must be 'needs_review'`);
7651
7651
  }
7652
- const traceId = nonEmptyString3(candidate.traceId, `${label}.traceId`);
7653
- const spanId = nonEmptyString3(candidate.spanId, `${label}.spanId`);
7654
- const evidenceUri = nonEmptyString3(candidate.evidenceUri, `${label}.evidenceUri`);
7652
+ const traceId = nonEmptyString3(candidate.traceId, `${label2}.traceId`);
7653
+ const spanId = nonEmptyString3(candidate.spanId, `${label2}.spanId`);
7654
+ const evidenceUri = nonEmptyString3(candidate.evidenceUri, `${label2}.evidenceUri`);
7655
7655
  if (evidenceUri !== spanEvidenceUri(traceId, spanId)) {
7656
- throw new TypeError(`${label}.evidenceUri does not match its traceId and spanId`);
7656
+ throw new TypeError(`${label2}.evidenceUri does not match its traceId and spanId`);
7657
7657
  }
7658
- validateSpanEvidence(evidenceUri, knownSpanUris, label);
7659
- const metadata = optionalRecord(candidate.metadata, `${label}.metadata`);
7658
+ validateSpanEvidence(evidenceUri, knownSpanUris, label2);
7659
+ const metadata = optionalRecord(candidate.metadata, `${label2}.metadata`);
7660
7660
  return {
7661
- engine: nonEmptyString3(candidate.engine, `${label}.engine`),
7662
- engineVersion: nonEmptyString3(candidate.engineVersion, `${label}.engineVersion`),
7661
+ engine: nonEmptyString3(candidate.engine, `${label2}.engine`),
7662
+ engineVersion: nonEmptyString3(candidate.engineVersion, `${label2}.engineVersion`),
7663
7663
  status: "needs_review",
7664
- group: nonEmptyString3(candidate.group, `${label}.group`),
7665
- rank: integer(candidate.rank, `${label}.rank`, 0),
7666
- groupSize: integer(candidate.groupSize, `${label}.groupSize`, 1),
7664
+ group: nonEmptyString3(candidate.group, `${label2}.group`),
7665
+ rank: integer(candidate.rank, `${label2}.rank`, 0),
7666
+ groupSize: integer(candidate.groupSize, `${label2}.groupSize`, 1),
7667
7667
  traceId,
7668
7668
  spanId,
7669
7669
  evidenceUri,
7670
- summary: nonEmptyString3(candidate.summary, `${label}.summary`),
7671
- actionText: nonEmptyString3(candidate.actionText, `${label}.actionText`),
7670
+ summary: nonEmptyString3(candidate.summary, `${label2}.summary`),
7671
+ actionText: nonEmptyString3(candidate.actionText, `${label2}.actionText`),
7672
7672
  ...metadata === void 0 ? {} : { metadata }
7673
7673
  };
7674
7674
  }
@@ -7702,57 +7702,57 @@ function decodeExternalAnalysisPayload(value) {
7702
7702
  throw new TypeError(`external analyzer payload.kind '${kind}' is unsupported`);
7703
7703
  }
7704
7704
  function decodeExternalAnalysisResult(value, analyzerName, knownSpanUris) {
7705
- const label = `external analyzer '${analyzerName}' result`;
7706
- const result = record(value, label);
7707
- const reportedAnalyzer = nonEmptyString3(result.analyzer, `${label}.analyzer`);
7705
+ const label2 = `external analyzer '${analyzerName}' result`;
7706
+ const result = record(value, label2);
7707
+ const reportedAnalyzer = nonEmptyString3(result.analyzer, `${label2}.analyzer`);
7708
7708
  if (reportedAnalyzer !== analyzerName) {
7709
7709
  throw new TypeError(
7710
7710
  `external analyzer '${analyzerName}' reported analyzer '${reportedAnalyzer}'`
7711
7711
  );
7712
7712
  }
7713
- const kind = nonEmptyString3(result.kind, `${label}.kind`);
7713
+ const kind = nonEmptyString3(result.kind, `${label2}.kind`);
7714
7714
  if (!["report", "findings", "discovery"].includes(kind)) {
7715
- throw new TypeError(`${label}.kind '${kind}' is unsupported`);
7715
+ throw new TypeError(`${label2}.kind '${kind}' is unsupported`);
7716
7716
  }
7717
- const ok = boolean(result.ok, `${label}.ok`);
7718
- const output = text(result.output, `${label}.output`);
7717
+ const ok = boolean(result.ok, `${label2}.ok`);
7718
+ const output = text(result.output, `${label2}.output`);
7719
7719
  if (!ok) {
7720
- assertOnlyKeys(result, RESULT_FAILURE_KEYS, label);
7720
+ assertOnlyKeys(result, RESULT_FAILURE_KEYS, label2);
7721
7721
  return {
7722
7722
  analyzer: analyzerName,
7723
7723
  kind,
7724
7724
  ok: false,
7725
7725
  output,
7726
- error: nonEmptyString3(result.error, `${label}.error`)
7726
+ error: nonEmptyString3(result.error, `${label2}.error`)
7727
7727
  };
7728
7728
  }
7729
7729
  if (kind === "report") {
7730
- assertOnlyKeys(result, RESULT_REPORT_KEYS, label);
7730
+ assertOnlyKeys(result, RESULT_REPORT_KEYS, label2);
7731
7731
  return { analyzer: analyzerName, kind, ok: true, output };
7732
7732
  }
7733
7733
  if (kind === "findings") {
7734
- assertOnlyKeys(result, RESULT_FINDING_KEYS, label);
7734
+ assertOnlyKeys(result, RESULT_FINDING_KEYS, label2);
7735
7735
  if (!Array.isArray(result.findings)) {
7736
- throw new TypeError(`${label}.findings must be an array`);
7736
+ throw new TypeError(`${label2}.findings must be an array`);
7737
7737
  }
7738
7738
  return {
7739
7739
  analyzer: analyzerName,
7740
7740
  kind,
7741
7741
  ok: true,
7742
7742
  output,
7743
- findings: result.findings.map((finding2, index) => decodeFinding(finding2, `${label}.findings[${index}]`, knownSpanUris))
7743
+ findings: result.findings.map((finding2, index) => decodeFinding(finding2, `${label2}.findings[${index}]`, knownSpanUris))
7744
7744
  };
7745
7745
  }
7746
- assertOnlyKeys(result, RESULT_DISCOVERY_KEYS, label);
7746
+ assertOnlyKeys(result, RESULT_DISCOVERY_KEYS, label2);
7747
7747
  if (!Array.isArray(result.candidates)) {
7748
- throw new TypeError(`${label}.candidates must be an array`);
7748
+ throw new TypeError(`${label2}.candidates must be an array`);
7749
7749
  }
7750
7750
  return {
7751
7751
  analyzer: analyzerName,
7752
7752
  kind: "discovery",
7753
7753
  ok: true,
7754
7754
  output,
7755
- candidates: result.candidates.map((candidate, index) => decodeCandidate(candidate, `${label}.candidates[${index}]`, knownSpanUris))
7755
+ candidates: result.candidates.map((candidate, index) => decodeCandidate(candidate, `${label2}.candidates[${index}]`, knownSpanUris))
7756
7756
  };
7757
7757
  }
7758
7758
 
@@ -8535,6 +8535,7 @@ function readPullRequests(spans) {
8535
8535
 
8536
8536
  // src/session-facts.ts
8537
8537
  import { OPENINFERENCE_SPAN_KIND as OPENINFERENCE_SPAN_KIND4, TOOL_NAME as TOOL_NAME5 } from "@tangle-network/agent-eval/trace-attributes";
8538
+ import { ingestSpans, rankFirstFailure } from "@tangle-network/agent-eval/diagnosis";
8538
8539
  var SESSION_TOKEN_TOTAL_ATTR = "traces.session.total_tokens";
8539
8540
  var SPAN_SYNTHESIZED_ATTR = "traces.codex.span_synthesized";
8540
8541
  var CORRUPTION_COUNT_ATTR = "traces.session.corruption_count";
@@ -8910,6 +8911,11 @@ function sessionFactsForTrace(traceId, sessionId, unordered) {
8910
8911
  changedFiles: changedFilesFact(files, truncatedInputs, unresolvedPaths),
8911
8912
  firstRecordAt: firstSpan ? { value: firstSpan.start_time, spanIds: [firstSpan.span_id], unavailable: null } : { value: null, spanIds: [], unavailable: "no span of this session's own records carries a parseable start time" },
8912
8913
  lastRecordAt: lastSpan ? { value: lastSpan.end_time, spanIds: [lastSpan.span_id], unavailable: null } : { value: null, spanIds: [], unavailable: "no span of this session's own records carries a parseable end time" },
8914
+ // rankFirstFailure throws on an empty list (distinct from "every span is
8915
+ // UNSET", which it reports as status 'unknown'); a trace whose spans are
8916
+ // all traces.span.subagent — every record folded into subagents' own
8917
+ // reports — legitimately has no records of its own to rank.
8918
+ firstFailure: ownSpans.length === 0 ? { status: "unknown", traceId, unsetCount: 0, reason: "no span of this session's own records" } : rankFirstFailure(ingestSpans(ownSpans, { contentIncluded: true }).spans),
8913
8919
  tokenTotal: tokenSpan ? {
8914
8920
  value: tokenSpan.attributes[SESSION_TOKEN_TOTAL_ATTR],
8915
8921
  spanIds: [tokenSpan.span_id],
@@ -8931,10 +8937,10 @@ function buildSessionFactsReport(spans, options = {}) {
8931
8937
  sessions: computeSessionFacts(spans)
8932
8938
  };
8933
8939
  }
8934
- function factLine(label, fact, rendered) {
8935
- if (fact.value === null) return ` ${label}: unavailable \u2014 ${fact.unavailable}`;
8940
+ function factLine(label2, fact, rendered) {
8941
+ if (fact.value === null) return ` ${label2}: unavailable \u2014 ${fact.unavailable}`;
8936
8942
  const partial = fact.partial ? ` (partial: ${fact.partial})` : "";
8937
- return ` ${label}: ${rendered}${partial}`;
8943
+ return ` ${label2}: ${rendered}${partial}`;
8938
8944
  }
8939
8945
  function renderSessionFacts(report) {
8940
8946
  const lines = [
@@ -8978,6 +8984,7 @@ function renderSessionFacts(report) {
8978
8984
  lines.push(factLine("first record", facts.firstRecordAt, String(facts.firstRecordAt.value)));
8979
8985
  lines.push(factLine("last record", facts.lastRecordAt, String(facts.lastRecordAt.value)));
8980
8986
  lines.push(factLine("token total", facts.tokenTotal, String(facts.tokenTotal.value)));
8987
+ lines.push(` first failure: ${renderFirstFailure(facts.firstFailure)}`);
8981
8988
  if ((facts.unreadRecords.value ?? 0) > 0) {
8982
8989
  lines.push(` unread records: ${facts.unreadRecords.value} (the facts above are computed from the rest)`);
8983
8990
  }
@@ -8988,6 +8995,15 @@ function renderSessionFacts(report) {
8988
8995
  return `${lines.join("\n")}
8989
8996
  `;
8990
8997
  }
8998
+ function renderFirstFailure(failure2) {
8999
+ if (failure2.status === "unknown") return `unknown \u2014 not recorded whether this run failed (${failure2.reason})`;
9000
+ if (failure2.status === "none") return `none recorded (${failure2.reason})`;
9001
+ if (failure2.status === "ambiguous") {
9002
+ return `ambiguous ${failure2.stage}: ${failure2.reason} (${failure2.candidates.join(", ")})`;
9003
+ }
9004
+ const message = failure2.message ? ` \u2014 ${failure2.message.split("\n")[0]?.slice(0, 160)}` : "";
9005
+ return `${failure2.stage} ${failure2.kind} "${failure2.name}" [${failure2.spanId}], blame ${failure2.blame}${message}`;
9006
+ }
8991
9007
  var PREPARED_CONTEXT_BYTE_CEILING = 3e4;
8992
9008
  function renderSessionFactsContext(report, options = {}) {
8993
9009
  const ceiling = options.byteCeiling ?? PREPARED_CONTEXT_BYTE_CEILING;
@@ -9135,7 +9151,26 @@ function compactFacts(facts) {
9135
9151
  },
9136
9152
  first_record_at: factJson(facts.firstRecordAt),
9137
9153
  last_record_at: factJson(facts.lastRecordAt),
9138
- token_total: factJson(facts.tokenTotal)
9154
+ token_total: factJson(facts.tokenTotal),
9155
+ first_failure: compactFirstFailure(facts.firstFailure)
9156
+ };
9157
+ }
9158
+ function compactFirstFailure(failure2) {
9159
+ if (failure2.status === "unknown") {
9160
+ return { status: "unknown", reason: failure2.reason, unset_count: failure2.unsetCount };
9161
+ }
9162
+ if (failure2.status === "none") return { status: "none", reason: failure2.reason };
9163
+ if (failure2.status === "ambiguous") {
9164
+ return { status: "ambiguous", stage: failure2.stage, reason: failure2.reason, span_ids: failure2.candidates };
9165
+ }
9166
+ return {
9167
+ status: "found",
9168
+ stage: failure2.stage,
9169
+ span_id: failure2.spanId,
9170
+ name: failure2.name,
9171
+ kind: failure2.kind,
9172
+ blame: failure2.blame,
9173
+ message: failure2.message
9139
9174
  };
9140
9175
  }
9141
9176
  function sessionFactsContext(spans, options = {}) {
@@ -9146,7 +9181,7 @@ function sessionFactsContext(spans, options = {}) {
9146
9181
  if (report.sessions.length === 0) return void 0;
9147
9182
  return renderSessionFactsContext(report, options);
9148
9183
  }
9149
- var SESSION_FACTS_VERSION_SUFFIX = "session-facts.1";
9184
+ var SESSION_FACTS_VERSION_SUFFIX = "session-facts.2";
9150
9185
  function withSessionFactsContext(definitions, spans, options = {}) {
9151
9186
  const sheet = sessionFactsContext(spans, options);
9152
9187
  if (sheet === void 0) return [...definitions];
@@ -9180,7 +9215,6 @@ import { dirname as dirname8, isAbsolute as isAbsolute4, join as join20, relativ
9180
9215
 
9181
9216
  // src/bundle.ts
9182
9217
  import { createHash as createHash8 } from "crypto";
9183
- import { readFileSync as readFileSync2 } from "fs";
9184
9218
  import { copyFile, cp, lstat, mkdir as mkdir2, readdir as readdir16, readFile as readFile7, stat as stat14, writeFile as writeFile5 } from "fs/promises";
9185
9219
  import { dirname as dirname7, join as join19, relative as relative4, resolve as resolve6, sep } from "path";
9186
9220
 
@@ -9207,34 +9241,13 @@ var SEVERITY_BADGE = {
9207
9241
  info: "\u2139\uFE0F INFO"
9208
9242
  };
9209
9243
  var CAPABILITY_ANALYSES = {
9210
- "token-accounting": {
9211
- analysis: "execution facts \u2192 Direct model usage (input/output/reasoning/cache token totals)",
9212
- built: true
9213
- },
9214
- "cost-attribution": {
9215
- analysis: "execution facts \u2192 Cost coverage (observed vs estimated USD)",
9216
- built: true
9217
- },
9218
- "tool-usage": {
9219
- analysis: "tool-usage table + per-tool duplicate/retry/error rates + stuck-loop detection",
9220
- built: true
9221
- },
9222
- "loop-convergence": {
9223
- analysis: "round-over-round convergence (did round N+1 improve on N)",
9224
- built: true
9225
- },
9226
- "tree-comparison": {
9227
- analysis: "arm-vs-arm comparison across branches of one run \u2014 NOT YET IMPLEMENTED in this package",
9228
- built: false
9229
- },
9230
- "steering-chain": {
9231
- analysis: "steering chain (which verdict caused which retry)",
9232
- built: true
9233
- },
9234
- "latency-analysis": {
9235
- analysis: "latency + critical-path timing distributions",
9236
- built: true
9237
- }
9244
+ "token-accounting": "execution facts \u2192 Direct model usage (input/output/reasoning/cache token totals)",
9245
+ "cost-attribution": "execution facts \u2192 Cost coverage (observed vs estimated USD)",
9246
+ "tool-usage": "tool-usage table + per-tool duplicate/retry/error rates + stuck-loop detection",
9247
+ "loop-convergence": "round-over-round convergence (did round N+1 improve on N)",
9248
+ "tree-comparison": "arm-vs-arm comparison: `traces diff <file>#branch=<a> <file>#branch=<b>` marks where two arms first diverge",
9249
+ "steering-chain": "steering chain (which verdict caused which retry)",
9250
+ "latency-analysis": "latency + critical-path timing distributions"
9238
9251
  };
9239
9252
  function unavailableCapabilities(validation) {
9240
9253
  return new Map(
@@ -9311,13 +9324,10 @@ function conformanceOfSpans(spans) {
9311
9324
  function skippedAnalyses(validation) {
9312
9325
  return validation.capabilities.filter((capability) => !capability.available).map((capability) => ({
9313
9326
  capability: capability.name,
9314
- analysis: CAPABILITY_ANALYSES[capability.name]?.analysis ?? "analyses keyed off this capability",
9327
+ analysis: CAPABILITY_ANALYSES[capability.name] ?? "analyses keyed off this capability",
9315
9328
  reason: capability.reason ?? "no reason recorded"
9316
9329
  }));
9317
9330
  }
9318
- function unbuiltAnalyses(validation) {
9319
- return validation.capabilities.filter((capability) => capability.available && CAPABILITY_ANALYSES[capability.name]?.built === false).map((capability) => capability.name);
9320
- }
9321
9331
  function tableCell(value) {
9322
9332
  return value.replace(/\s+/g, " ").replaceAll("|", "\\|").trim();
9323
9333
  }
@@ -9328,12 +9338,8 @@ function spanIdSample(finding2) {
9328
9338
  return ids.length > SPAN_ID_SAMPLE ? `${shown} +${ids.length - SPAN_ID_SAMPLE} more` : shown;
9329
9339
  }
9330
9340
  function capabilityRow(capability) {
9331
- const entry = CAPABILITY_ANALYSES[capability.name];
9332
- const analysis = entry?.analysis ?? "\u2014";
9333
- if (!capability.available) {
9334
- return `| \`${capability.name}\` | \u274C unavailable | ${tableCell(analysis)} | ${tableCell(capability.reason ?? "no reason recorded")} |`;
9335
- }
9336
- return entry?.built === false ? `| \`${capability.name}\` | \u26A0\uFE0F available, unused | ${tableCell(analysis)} | the trace supports it; this package has no analysis for it yet |` : `| \`${capability.name}\` | \u2705 available | ${tableCell(analysis)} | \u2014 |`;
9341
+ const analysis = CAPABILITY_ANALYSES[capability.name] ?? "\u2014";
9342
+ return capability.available ? `| \`${capability.name}\` | \u2705 available | ${tableCell(analysis)} | \u2014 |` : `| \`${capability.name}\` | \u274C unavailable | ${tableCell(analysis)} | ${tableCell(capability.reason ?? "no reason recorded")} |`;
9337
9343
  }
9338
9344
  function renderInheritedLoss(lines, unreadable) {
9339
9345
  if (unreadable === void 0 || unreadable.inherited === 0) return;
@@ -9456,26 +9462,17 @@ function conformanceBody(validation, opts) {
9456
9462
  for (const capability of validation.capabilities) lines.push(capabilityRow(capability));
9457
9463
  lines.push("");
9458
9464
  const skipped = skippedAnalyses(validation);
9459
- const unbuilt = unbuiltAnalyses(validation);
9460
- if (skipped.length > 0 || unbuilt.length > 0) {
9465
+ if (skipped.length > 0) {
9461
9466
  lines.push(`${heading}# analyses skipped, and why`);
9462
9467
  lines.push("");
9463
- if (skipped.length > 0) {
9464
- lines.push(
9465
- "These reported nothing because the trace never carried what they read \u2014 not because the run was uneventful. Every section they feed is marked at the table, not only here."
9466
- );
9467
- lines.push("");
9468
- for (const entry of skipped) {
9469
- lines.push(`- **${entry.analysis}** \u2014 skipped: ${entry.reason} (\`${entry.capability}\`).`);
9470
- }
9471
- lines.push("");
9472
- }
9473
- if (unbuilt.length > 0) {
9474
- lines.push(
9475
- `These are supported by the trace and **not yet implemented here**: ${unbuilt.map((name) => `\`${name}\``).join(", ")}. The spans carry what they need; the analysis does not exist, so nothing about them appears below.`
9476
- );
9477
- lines.push("");
9468
+ lines.push(
9469
+ "These reported nothing because the trace never carried what they read \u2014 not because the run was uneventful. Every section they feed is marked at the table, not only here."
9470
+ );
9471
+ lines.push("");
9472
+ for (const entry of skipped) {
9473
+ lines.push(`- **${entry.analysis}** \u2014 skipped: ${entry.reason} (\`${entry.capability}\`).`);
9478
9474
  }
9475
+ lines.push("");
9479
9476
  }
9480
9477
  return lines.join("\n");
9481
9478
  }
@@ -9840,10 +9837,10 @@ async function readLinuxProcProcessTable() {
9840
9837
  const rows = await Promise.all(
9841
9838
  entries.filter((entry) => entry.isDirectory() && /^\d+$/.test(entry.name)).map(async (entry) => {
9842
9839
  try {
9843
- const stat18 = await readFile5(`/proc/${entry.name}/stat`, "utf8");
9844
- const commandEnd = stat18.lastIndexOf(")");
9840
+ const stat19 = await readFile5(`/proc/${entry.name}/stat`, "utf8");
9841
+ const commandEnd = stat19.lastIndexOf(")");
9845
9842
  if (commandEnd < 0) return void 0;
9846
- const fields = stat18.slice(commandEnd + 1).trim().split(/\s+/);
9843
+ const fields = stat19.slice(commandEnd + 1).trim().split(/\s+/);
9847
9844
  const pid = Number(entry.name);
9848
9845
  const parentPid = Number(fields[1]);
9849
9846
  if (!Number.isSafeInteger(pid) || pid < 1 || !Number.isSafeInteger(parentPid) || parentPid < 0) {
@@ -10062,16 +10059,16 @@ async function spanUrisFromArtifact(otlpPath, signal) {
10062
10059
  const uris = /* @__PURE__ */ new Set();
10063
10060
  let index = 0;
10064
10061
  for await (const value of readJsonl(otlpPath, { signal })) {
10065
- const label = `${otlpPath}:${index + 1}`;
10062
+ const label2 = `${otlpPath}:${index + 1}`;
10066
10063
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
10067
- throw new TypeError(`${label} must contain an object`);
10064
+ throw new TypeError(`${label2} must contain an object`);
10068
10065
  }
10069
10066
  const row = value;
10070
10067
  if (typeof row.trace_id !== "string" || row.trace_id.trim().length === 0) {
10071
- throw new TypeError(`${label}.trace_id must be a non-empty string`);
10068
+ throw new TypeError(`${label2}.trace_id must be a non-empty string`);
10072
10069
  }
10073
10070
  if (typeof row.span_id !== "string" || row.span_id.trim().length === 0) {
10074
- throw new TypeError(`${label}.span_id must be a non-empty string`);
10071
+ throw new TypeError(`${label2}.span_id must be a non-empty string`);
10075
10072
  }
10076
10073
  uris.add(spanEvidenceUri(row.trace_id, row.span_id));
10077
10074
  index += 1;
@@ -10444,10 +10441,10 @@ function decimal(value, digits = 1) {
10444
10441
  function measured(value, render) {
10445
10442
  return value === null ? "not captured" : render(value);
10446
10443
  }
10447
- function distributionRow(label, distribution, unit = "") {
10448
- if (distribution.n === 0) return `| ${label} | 0 | not captured | not captured | not captured | not captured | not captured |`;
10444
+ function distributionRow(label2, distribution, unit = "") {
10445
+ if (distribution.n === 0) return `| ${label2} | 0 | not captured | not captured | not captured | not captured | not captured |`;
10449
10446
  const value = (number) => measured(number, (captured) => `${decimal(captured)}${unit}`);
10450
- return `| ${label} | ${count(distribution.n)} | ${value(distribution.min)} | ${value(distribution.mean)} | ${value(distribution.p50)} | ${value(distribution.p95)} | ${value(distribution.max)} |`;
10447
+ return `| ${label2} | ${count(distribution.n)} | ${value(distribution.min)} | ${value(distribution.mean)} | ${value(distribution.p50)} | ${value(distribution.p95)} | ${value(distribution.max)} |`;
10451
10448
  }
10452
10449
  function tokenRows(usage) {
10453
10450
  const rows = [
@@ -10458,7 +10455,7 @@ function tokenRows(usage) {
10458
10455
  ["Cache write", usage.totals.cacheWrite, usage.cacheWrite]
10459
10456
  ];
10460
10457
  return rows.map(
10461
- ([label, total, values]) => `| ${label} | ${count(total)} | ${count(values.n)} | ${measured(values.mean, decimal)} | ${measured(values.p50, decimal)} | ${measured(values.p95, decimal)} | ${measured(values.max, count)} |`
10458
+ ([label2, total, values]) => `| ${label2} | ${count(total)} | ${count(values.n)} | ${measured(values.mean, decimal)} | ${measured(values.p50, decimal)} | ${measured(values.p95, decimal)} | ${measured(values.max, count)} |`
10462
10459
  );
10463
10460
  }
10464
10461
  function analysisCost(result) {
@@ -10951,9 +10948,9 @@ function renderAdoption(ar) {
10951
10948
  "- **Prompt, tools, MCP, hooks, and full agent profile:** not assessed; no baseline profile was supplied."
10952
10949
  );
10953
10950
  if (ar.skillPenetration === null) {
10954
- const label = ar.skillTelemetryStatus === "unsupported" ? "uncaptured/unsupported" : "uncaptured/unknown";
10951
+ const label2 = ar.skillTelemetryStatus === "unsupported" ? "uncaptured/unsupported" : "uncaptured/unknown";
10955
10952
  lines.push(
10956
- `- **Explicit skill invocation rate:** ${label} (${ar.skillTelemetrySessions}/${ar.executionGroupCount} observed group(s) expose a dedicated Skill event)`
10953
+ `- **Explicit skill invocation rate:** ${label2} (${ar.skillTelemetrySessions}/${ar.executionGroupCount} observed group(s) expose a dedicated Skill event)`
10957
10954
  );
10958
10955
  } else {
10959
10956
  const excluded = ar.executionGroupCount - ar.skillTelemetrySessions;
@@ -12257,6 +12254,17 @@ async function collectSessionSelection(groups, options = {}) {
12257
12254
  return { rows, ...workflow ? { workflow } : {} };
12258
12255
  }
12259
12256
 
12257
+ // src/version.ts
12258
+ import { readFileSync as readFileSync2 } from "fs";
12259
+ var cached;
12260
+ function tracesVersion() {
12261
+ if (cached) return cached;
12262
+ const pkg = JSON.parse(readFileSync2(new URL("../package.json", import.meta.url), "utf8"));
12263
+ if (typeof pkg.version !== "string" || !pkg.version) throw new Error("package.json is missing version");
12264
+ cached = pkg.version;
12265
+ return cached;
12266
+ }
12267
+
12260
12268
  // src/bundle.ts
12261
12269
  var SESSION_WINDOW_PAD_MS = 15 * 60 * 1e3;
12262
12270
  var KNOWN_LIMITS = [
@@ -12549,7 +12557,7 @@ async function assembleSessionBundle(opts) {
12549
12557
  transcriptPath: resolve6(ref.path),
12550
12558
  transcriptSha256,
12551
12559
  contextRoot,
12552
- tracesVersion: tracesPackageVersion(),
12560
+ tracesVersion: tracesVersion(),
12553
12561
  sessionWindow: { firstSpanAt, lastSpanAt, padMs: SESSION_WINDOW_PAD_MS }
12554
12562
  },
12555
12563
  files,
@@ -12693,16 +12701,6 @@ async function verifySessionBundle(bundleDir) {
12693
12701
  ...source ? { source } : {}
12694
12702
  };
12695
12703
  }
12696
- var cachedVersion;
12697
- function tracesPackageVersion() {
12698
- if (cachedVersion) return cachedVersion;
12699
- const pkg = JSON.parse(
12700
- readFileSync2(new URL("../package.json", import.meta.url), "utf8")
12701
- );
12702
- if (typeof pkg.version !== "string" || !pkg.version) throw new Error("package.json is missing version");
12703
- cachedVersion = pkg.version;
12704
- return cachedVersion;
12705
- }
12706
12704
 
12707
12705
  // src/bundle-source.ts
12708
12706
  var SHA256 = /^[a-f0-9]{64}$/;
@@ -13330,9 +13328,9 @@ async function containedDirectory(path) {
13330
13328
  }
13331
13329
  return canonical;
13332
13330
  }
13333
- async function containedFile(root, candidate, label) {
13331
+ async function containedFile(root, candidate, label2) {
13334
13332
  const path = await optionalContainedFile(root, candidate);
13335
- if (!path) throw new Error(`${label} is missing`);
13333
+ if (!path) throw new Error(`${label2} is missing`);
13336
13334
  return path;
13337
13335
  }
13338
13336
  async function optionalContainedFile(root, candidate) {
@@ -13564,49 +13562,49 @@ function parseSteps(text2, row) {
13564
13562
  );
13565
13563
  }
13566
13564
  return parsed.map((value, index) => {
13567
- const label = `${row.trajId}/steps.json[${index}]`;
13568
- if (!isObject3(value)) throw new TypeError(`${label} must be an object`);
13569
- const stepId = positiveInteger2(value.step_id, `${label}.step_id`);
13565
+ const label2 = `${row.trajId}/steps.json[${index}]`;
13566
+ if (!isObject3(value)) throw new TypeError(`${label2} must be an object`);
13567
+ const stepId = positiveInteger2(value.step_id, `${label2}.step_id`);
13570
13568
  if (stepId !== index + 1) {
13571
- throw new Error(`${label}.step_id is ${stepId}; expected ${index + 1}`);
13569
+ throw new Error(`${label2}.step_id is ${stepId}; expected ${index + 1}`);
13572
13570
  }
13573
13571
  if (!Object.hasOwn(value, "observation")) {
13574
- throw new TypeError(`${label}.observation is required and may be null`);
13572
+ throw new TypeError(`${label2}.observation is required and may be null`);
13575
13573
  }
13576
13574
  const observation = value.observation;
13577
13575
  if (observation !== null && typeof observation !== "string") {
13578
- throw new TypeError(`${label}.observation must be a string or null`);
13576
+ throw new TypeError(`${label2}.observation must be a string or null`);
13579
13577
  }
13580
13578
  return {
13581
13579
  step_id: stepId,
13582
- action: requiredString(value.action, `${label}.action`),
13580
+ action: requiredString(value.action, `${label2}.action`),
13583
13581
  observation,
13584
- ...value.thinking === void 0 ? {} : { thinking: requiredString(value.thinking, `${label}.thinking`) },
13582
+ ...value.thinking === void 0 ? {} : { thinking: requiredString(value.thinking, `${label2}.thinking`) },
13585
13583
  ...value.parallel_group === void 0 ? {} : {
13586
13584
  parallel_group: nonNegativeInteger(
13587
13585
  value.parallel_group,
13588
- `${label}.parallel_group`
13586
+ `${label2}.parallel_group`
13589
13587
  )
13590
13588
  },
13591
- ...value.tool_type === void 0 ? {} : { tool_type: requiredString(value.tool_type, `${label}.tool_type`) },
13592
- action_ref: fileRef(value.action_ref, `${label}.action_ref`),
13593
- observation_ref: fileRef(value.observation_ref, `${label}.observation_ref`)
13589
+ ...value.tool_type === void 0 ? {} : { tool_type: requiredString(value.tool_type, `${label2}.tool_type`) },
13590
+ action_ref: fileRef(value.action_ref, `${label2}.action_ref`),
13591
+ observation_ref: fileRef(value.observation_ref, `${label2}.observation_ref`)
13594
13592
  };
13595
13593
  });
13596
13594
  }
13597
- function fileRef(value, label) {
13595
+ function fileRef(value, label2) {
13598
13596
  if (value === null) return null;
13599
- if (!isObject3(value)) throw new TypeError(`${label} must be an object or null`);
13600
- const lineStart = positiveInteger2(value.line_start, `${label}.line_start`);
13601
- const lineEnd = positiveInteger2(value.line_end, `${label}.line_end`);
13597
+ if (!isObject3(value)) throw new TypeError(`${label2} must be an object or null`);
13598
+ const lineStart = positiveInteger2(value.line_start, `${label2}.line_start`);
13599
+ const lineEnd = positiveInteger2(value.line_end, `${label2}.line_end`);
13602
13600
  if (lineEnd < lineStart) {
13603
- throw new RangeError(`${label}.line_end must not precede line_start`);
13601
+ throw new RangeError(`${label2}.line_end must not precede line_start`);
13604
13602
  }
13605
13603
  return {
13606
- path: requiredString(value.path, `${label}.path`),
13604
+ path: requiredString(value.path, `${label2}.path`),
13607
13605
  line_start: lineStart,
13608
13606
  line_end: lineEnd,
13609
- content: stringField(value.content, `${label}.content`)
13607
+ content: stringField(value.content, `${label2}.content`)
13610
13608
  };
13611
13609
  }
13612
13610
  async function readOptionalTask(trajectoryDirectory, caseDirectory, row, signal) {
@@ -13663,26 +13661,26 @@ function normalizedLabels(value) {
13663
13661
  function compact(value) {
13664
13662
  return value.replace(/\s+/g, "");
13665
13663
  }
13666
- function positiveInteger2(value, label) {
13664
+ function positiveInteger2(value, label2) {
13667
13665
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) {
13668
- throw new TypeError(`${label} must be a positive integer`);
13666
+ throw new TypeError(`${label2} must be a positive integer`);
13669
13667
  }
13670
13668
  return value;
13671
13669
  }
13672
- function nonNegativeInteger(value, label) {
13670
+ function nonNegativeInteger(value, label2) {
13673
13671
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
13674
- throw new TypeError(`${label} must be a non-negative integer`);
13672
+ throw new TypeError(`${label2} must be a non-negative integer`);
13675
13673
  }
13676
13674
  return value;
13677
13675
  }
13678
- function requiredString(value, label) {
13676
+ function requiredString(value, label2) {
13679
13677
  if (typeof value !== "string" || !value.trim()) {
13680
- throw new TypeError(`${label} must be a non-empty string`);
13678
+ throw new TypeError(`${label2} must be a non-empty string`);
13681
13679
  }
13682
13680
  return value.trim();
13683
13681
  }
13684
- function stringField(value, label) {
13685
- if (typeof value !== "string") throw new TypeError(`${label} must be a string`);
13682
+ function stringField(value, label2) {
13683
+ if (typeof value !== "string") throw new TypeError(`${label2} must be a string`);
13686
13684
  return value;
13687
13685
  }
13688
13686
  function errorMessage(error) {
@@ -15026,16 +15024,16 @@ async function projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, si
15026
15024
  const spanTraces = /* @__PURE__ */ new Map();
15027
15025
  let index = 0;
15028
15026
  for await (const value of readJsonl(otlpPath, { signal })) {
15029
- const label = `${otlpPath}:${index + 1}`;
15027
+ const label2 = `${otlpPath}:${index + 1}`;
15030
15028
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
15031
- throw new TypeError(`${label} must contain an object`);
15029
+ throw new TypeError(`${label2} must contain an object`);
15032
15030
  }
15033
15031
  const row = value;
15034
15032
  if (typeof row.trace_id !== "string" || row.trace_id.length === 0) {
15035
- throw new TypeError(`${label}.trace_id must be a non-empty string`);
15033
+ throw new TypeError(`${label2}.trace_id must be a non-empty string`);
15036
15034
  }
15037
15035
  if (typeof row.span_id !== "string" || row.span_id.length === 0) {
15038
- throw new TypeError(`${label}.span_id must be a non-empty string`);
15036
+ throw new TypeError(`${label2}.span_id must be a non-empty string`);
15039
15037
  }
15040
15038
  rows.push(row);
15041
15039
  const traces = spanTraces.get(row.span_id) ?? /* @__PURE__ */ new Set();
@@ -15377,53 +15375,55 @@ function primeAnalyzer(opts = {}) {
15377
15375
  }
15378
15376
 
15379
15377
  // src/redact.ts
15380
- import { DEFAULT_REDACTION_RULES, redactValue } from "@tangle-network/agent-eval/traces";
15381
- var CODING_REDACTION_RULES = [
15382
- { id: "github-token", pattern: /\b(?:gh[pousr]_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{22,})\b/g },
15383
- { id: "aws-akid", pattern: /\bAKIA[0-9A-Z]{16}\b/g },
15384
- { id: "slack-token", pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
15385
- { id: "jwt", pattern: /\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g },
15386
- { id: "bearer", pattern: /\bBearer\s+[A-Za-z0-9._-]{16,}/gi },
15387
- {
15388
- id: "private-key",
15389
- pattern: /-----BEGIN(?:[A-Z ]+)?PRIVATE KEY-----[\s\S]*?-----END(?:[A-Z ]+)?PRIVATE KEY-----/g
15390
- },
15391
- // Match the whole identifier because underscores prevent a word boundary before a secret suffix.
15392
- {
15393
- id: "assigned-secret",
15394
- pattern: /(?<![A-Za-z0-9_-])(?:[A-Za-z_][A-Za-z0-9_-]*)?(?:api[_-]?key|secret|token|password|passwd|access[_-]?token|client[_-]?secret)(?![A-Za-z0-9_-])\+?=(?:\$\((?:\\.|[^\\\r\n])*\)|\$\{(?:\\.|[^}\\\r\n])*\}|`(?:\\.|[^`\\\r\n])*`|\$?'(?:\\.|[^'\\\r\n])*'|\$?"(?:\\.|[^"\\\r\n])*"|\\[^\r\n]|[^\s;&|<>()"'`\\\r\n])+/gi
15395
- },
15396
- // Config-file secrets (YAML/INI line keys, JSON quoted keys) — the coverage the shell
15397
- // rule above deliberately gave up to spare prose ("Narrative token: x") and type
15398
- // annotations ("(token: string)"). The line anchor is the discriminator: config keys
15399
- // start their line, prose colons never do; JSON keys are safe anywhere because both
15400
- // sides are quoted. Identifiers must END in a secret word, same as the shell rule.
15401
- {
15402
- id: "config-secret",
15403
- pattern: /(?:^[ \t]*["']?(?:[A-Za-z_][A-Za-z0-9_-]*)?(?:api[_-]?key|secret|token|password|passwd|access[_-]?token|client[_-]?secret)(?![A-Za-z0-9_-])["']?[ \t]*[:=][ \t]*|["'](?:[A-Za-z_][A-Za-z0-9_-]*)?(?:api[_-]?key|secret|token|password|passwd|access[_-]?token|client[_-]?secret)["']\s*:\s*)["']?[A-Za-z0-9._\-]{12,}["']?/gim
15404
- },
15405
- // Credentials embedded in URLs — common when a prompt pastes a curl/clone line.
15406
- { id: "url-userinfo", pattern: /\b[a-z][a-z0-9+.-]*:\/\/[^\s/:@]+:[^\s/@]+@/gi },
15407
- {
15408
- id: "url-secret-param",
15409
- pattern: /[?&](?:access[_-]?token|api[_-]?key|apikey|auth|token|secret|password|sig|signature)=[^&\s"'#)]+/gi
15410
- }
15411
- ];
15412
- var TRACES_REDACTION_RULES = [...DEFAULT_REDACTION_RULES, ...CODING_REDACTION_RULES];
15413
- function redactSpans(spans, rules = TRACES_REDACTION_RULES) {
15414
- const report = { redactionCount: 0, byRule: {} };
15415
- const out = spans.map((s) => {
15416
- const attributes2 = redactValue(stripSourceAttributes(s.attributes), rules, report).value;
15417
- normalizeToolIoAttributes(attributes2);
15418
- let status = s.status;
15419
- if (status.message) {
15420
- const m = redactValue(status.message, rules, report).value;
15421
- if (typeof m === "string" && m !== status.message) status = { ...status, message: m };
15422
- }
15423
- return { ...s, attributes: attributes2, status };
15378
+ import {
15379
+ assessShareSafety,
15380
+ combineVerdicts,
15381
+ redact
15382
+ } from "@tangle-network/agent-eval/traces";
15383
+ function redactSpans(spans, options = {}) {
15384
+ const parts = spans.map((span2) => ({
15385
+ name: span2.name,
15386
+ attributes: stripSourceAttributes(span2.attributes),
15387
+ message: span2.status.message,
15388
+ links: span2.links?.map((link2) => ({
15389
+ ...link2,
15390
+ ...link2.attributes ? { attributes: stripSourceAttributes(link2.attributes) } : {}
15391
+ }))
15392
+ }));
15393
+ const { value, report } = redact(parts, options);
15394
+ const out = spans.map((span2, index) => {
15395
+ const part = value[index];
15396
+ normalizeToolIoAttributes(part.attributes);
15397
+ const status = part.message !== void 0 && part.message !== span2.status.message ? { ...span2.status, message: part.message } : span2.status;
15398
+ return {
15399
+ ...span2,
15400
+ name: part.name,
15401
+ attributes: part.attributes,
15402
+ status,
15403
+ ...part.links ? { links: part.links } : {}
15404
+ };
15424
15405
  });
15425
15406
  return { spans: out, report };
15426
15407
  }
15408
+ function assessSpans(spans, profile = "default") {
15409
+ return combineVerdicts(
15410
+ profile,
15411
+ spans.map((span2) => {
15412
+ const verdict = assessShareSafety(
15413
+ { name: span2.name, attributes: span2.attributes, status: span2.status, links: span2.links },
15414
+ { profile }
15415
+ );
15416
+ return {
15417
+ ...verdict,
15418
+ findings: verdict.findings.map((finding2) => ({
15419
+ ...finding2,
15420
+ paths: finding2.paths.map((path) => `span:${span2.span_id}${path}`)
15421
+ })),
15422
+ unreadable: verdict.unreadable.map((entry) => `span:${span2.span_id}${entry}`)
15423
+ };
15424
+ })
15425
+ );
15426
+ }
15427
15427
  async function applyRedactor(spans, redactor) {
15428
15428
  const capturedKeys = ["content", ...TOOL_IO_VALUE_KEYS];
15429
15429
  const fields = [];
@@ -16089,7 +16089,7 @@ function exportTraceEvidenceRows(rows, opts = {}, wrapper) {
16089
16089
  format,
16090
16090
  spans: validated,
16091
16091
  redactionCount: redacted.report.redactionCount,
16092
- redactionsByRule: redacted.report.byRule
16092
+ redactionsByDetector: redacted.report.byDetector
16093
16093
  };
16094
16094
  }
16095
16095
  function exportTraceEvidenceText(text2, opts = {}) {
@@ -16102,7 +16102,7 @@ async function exportTraceEvidenceFile(inputPath, opts = {}) {
16102
16102
  const spans = [];
16103
16103
  const sandboxRows = [];
16104
16104
  let redactionCount = 0;
16105
- const redactionsByRule = {};
16105
+ const redactionsByDetector = {};
16106
16106
  for await (const row of readJsonl(inputPath)) {
16107
16107
  format ??= detectFormat([row], opts.format ?? "auto");
16108
16108
  if (format === "sandbox-events") {
@@ -16116,8 +16116,8 @@ async function exportTraceEvidenceFile(inputPath, opts = {}) {
16116
16116
  });
16117
16117
  appendAll(spans, converted.spans);
16118
16118
  redactionCount += converted.redactionCount;
16119
- for (const [rule, count2] of Object.entries(converted.redactionsByRule)) {
16120
- redactionsByRule[rule] = (redactionsByRule[rule] ?? 0) + count2;
16119
+ for (const [rule, count2] of Object.entries(converted.redactionsByDetector)) {
16120
+ redactionsByDetector[rule] = (redactionsByDetector[rule] ?? 0) + count2;
16121
16121
  }
16122
16122
  }
16123
16123
  if (!format) throw new Error("input file is empty");
@@ -16132,7 +16132,7 @@ async function exportTraceEvidenceFile(inputPath, opts = {}) {
16132
16132
  format,
16133
16133
  spans: validateOtlpSpans(spans, `${format} file export`),
16134
16134
  redactionCount,
16135
- redactionsByRule
16135
+ redactionsByDetector
16136
16136
  };
16137
16137
  }
16138
16138
  const text2 = await readFile12(inputPath, "utf8");
@@ -16550,8 +16550,8 @@ function sessionFrom(spans, opts) {
16550
16550
  ...opts.session?.path ? { path: opts.session.path } : {}
16551
16551
  };
16552
16552
  }
16553
- function evidence2(kind, label, value, spanIds) {
16554
- return { kind, label, value, ...spanIds && spanIds.length > 0 ? { spanIds } : {} };
16553
+ function evidence2(kind, label2, value, spanIds) {
16554
+ return { kind, label: label2, value, ...spanIds && spanIds.length > 0 ? { spanIds } : {} };
16555
16555
  }
16556
16556
  function finding(input) {
16557
16557
  const fingerprint = hash([input.ruleId, ...input.signature]);
@@ -17428,8 +17428,8 @@ function buildSpanRunTree(spans, source) {
17428
17428
  for (const root of childrenOf.get(null) ?? []) walk(root, null, null, 0);
17429
17429
  const subtotal = /* @__PURE__ */ new Map();
17430
17430
  const subtotalOf = (key) => {
17431
- const cached = subtotal.get(key);
17432
- if (cached !== void 0) return cached;
17431
+ const cached2 = subtotal.get(key);
17432
+ if (cached2 !== void 0) return cached2;
17433
17433
  const node = building.get(key);
17434
17434
  if (node === void 0) return ZERO_ROLL;
17435
17435
  subtotal.set(key, ZERO_ROLL);
@@ -18967,6 +18967,169 @@ async function projectSessionBundle(opts) {
18967
18967
  return { directory: outDir, manifestPath, manifest, leakCheck };
18968
18968
  }
18969
18969
 
18970
+ // src/diff.ts
18971
+ import { stat as stat18 } from "fs/promises";
18972
+ import { ATTR as ATTR5 } from "@tangle-network/agent-trace-contract";
18973
+ import { ingestSpans as ingestSpans2 } from "@tangle-network/agent-eval/diagnosis";
18974
+ import { diffSteps, diffStepsFromSpans } from "@tangle-network/agent-eval/pipelines";
18975
+ async function readSpans(path) {
18976
+ const otlp = await readOtlpInput(path);
18977
+ if (otlp.spans.length > 0) return [...otlp.spans];
18978
+ if (!(await stat18(path)).isFile()) {
18979
+ throw new Error(`${path}: no OTLP spans found (${otlp.issues.length} unusable row(s))`);
18980
+ }
18981
+ return (await exportTraceEvidenceFile(path)).spans;
18982
+ }
18983
+ function branchSpans(spans, branch) {
18984
+ const byId = /* @__PURE__ */ new Map();
18985
+ for (const span2 of spans) byId.set(span2.span_id, span2);
18986
+ const children = /* @__PURE__ */ new Map();
18987
+ for (const span2 of spans) {
18988
+ if (!span2.parent_span_id) continue;
18989
+ const list = children.get(span2.parent_span_id);
18990
+ if (list) list.push(span2);
18991
+ else children.set(span2.parent_span_id, [span2]);
18992
+ }
18993
+ const matched = spans.filter(
18994
+ (span2) => span2.attributes[ATTR5.branchId] === branch || span2.attributes[ATTR5.branchArm] === branch
18995
+ );
18996
+ const matchedIds = new Set(matched.map((span2) => span2.span_id));
18997
+ const hasMatchedAncestor = (span2) => {
18998
+ let current = span2;
18999
+ while (current.parent_span_id) {
19000
+ const parent = byId.get(current.parent_span_id);
19001
+ if (!parent) return false;
19002
+ if (matchedIds.has(parent.span_id)) return true;
19003
+ current = parent;
19004
+ }
19005
+ return false;
19006
+ };
19007
+ const roots = matched.filter((span2) => !hasMatchedAncestor(span2));
19008
+ if (roots.length > 1) {
19009
+ const named = roots.map((span2) => `${span2.span_id} (${ATTR5.branchId}=${JSON.stringify(span2.attributes[ATTR5.branchId] ?? null)})`).join(", ");
19010
+ throw new Error(
19011
+ `"${branch}" matches ${roots.length} separate subtrees, not one arm: ${named} \u2014 sibling arms can share a branch id; select by span id, or narrow the input to one arm first`
19012
+ );
19013
+ }
19014
+ const selected = /* @__PURE__ */ new Set();
19015
+ const stack = [...matched];
19016
+ while (stack.length > 0) {
19017
+ const span2 = stack.pop();
19018
+ if (selected.has(span2)) continue;
19019
+ selected.add(span2);
19020
+ stack.push(...children.get(span2.span_id) ?? []);
19021
+ }
19022
+ return spans.filter((span2) => selected.has(span2));
19023
+ }
19024
+ async function readRunSide(ref, options = {}) {
19025
+ const hash2 = ref.lastIndexOf("#");
19026
+ const path = hash2 > 0 ? ref.slice(0, hash2) : ref;
19027
+ const selector = hash2 > 0 ? ref.slice(hash2 + 1) : void 0;
19028
+ const branch = selector?.startsWith("branch=") ? selector.slice("branch=".length) : void 0;
19029
+ const all = await readSpans(path);
19030
+ let spans;
19031
+ let traceId;
19032
+ if (branch !== void 0) {
19033
+ spans = branchSpans(all, branch);
19034
+ if (spans.length === 0) throw new Error(`${path} holds no span with ${ATTR5.branchId} or ${ATTR5.branchArm} "${branch}"`);
19035
+ const traces = new Set(spans.map((span2) => span2.trace_id));
19036
+ if (traces.size !== 1) throw new Error(`${path}: branch "${branch}" appears in ${traces.size} runs; export one run per file first`);
19037
+ traceId = [...traces][0];
19038
+ } else {
19039
+ const byTrace = /* @__PURE__ */ new Map();
19040
+ for (const span2 of all) {
19041
+ const list = byTrace.get(span2.trace_id);
19042
+ if (list) list.push(span2);
19043
+ else byTrace.set(span2.trace_id, [span2]);
19044
+ }
19045
+ if (selector === void 0 && byTrace.size !== 1) {
19046
+ throw new Error(
19047
+ `${path} holds ${byTrace.size} runs; pick one with ${path}#<trace id>: ${[...byTrace.keys()].slice(0, 10).join(", ")}`
19048
+ );
19049
+ }
19050
+ traceId = selector ?? [...byTrace.keys()][0];
19051
+ const picked = byTrace.get(traceId);
19052
+ if (!picked) throw new Error(`${path} holds no run with trace id ${traceId}`);
19053
+ spans = picked;
19054
+ }
19055
+ const { spans: ingested } = ingestSpans2(spans, { contentIncluded: false });
19056
+ const kinds = options.kinds?.length ? new Set(options.kinds.map((kind) => kind.toUpperCase())) : null;
19057
+ const steps = diffStepsFromSpans(ingested).filter((step) => kinds === null || kinds.has(step.kind));
19058
+ return { path, traceId, ...branch === void 0 ? {} : { branch }, spans: spans.length, steps };
19059
+ }
19060
+ function hasDefiniteStatus(steps) {
19061
+ return steps.some((step) => step.fields.status === "OK" || step.fields.status === "ERROR");
19062
+ }
19063
+ function hasToolStep(steps) {
19064
+ return steps.some((step) => step.kind === "TOOL");
19065
+ }
19066
+ function agreementCaveat(a, b) {
19067
+ const reasons = [];
19068
+ if (!hasDefiniteStatus(a) && !hasDefiniteStatus(b)) {
19069
+ reasons.push("no step on either side carries a definitive OK or ERROR status (all UNSET)");
19070
+ }
19071
+ if (!hasToolStep(a) && !hasToolStep(b)) {
19072
+ reasons.push("no TOOL step on either side");
19073
+ }
19074
+ return reasons.length > 0 ? reasons.join("; ") : void 0;
19075
+ }
19076
+ async function diffRuns(a, b, options = {}) {
19077
+ const [left, right] = await Promise.all([readRunSide(a, options), readRunSide(b, options)]);
19078
+ const diff = diffSteps(left.steps, right.steps);
19079
+ const caveat = diff.firstDivergence ? void 0 : agreementCaveat(left.steps, right.steps);
19080
+ return {
19081
+ kind: "traces.run_diff",
19082
+ ...options.kinds?.length ? { kinds: options.kinds.map((kind) => kind.toUpperCase()) } : {},
19083
+ a: left,
19084
+ b: right,
19085
+ diff,
19086
+ ...caveat ? { caveat } : {}
19087
+ };
19088
+ }
19089
+ var TEXT_STEP_LIMIT = 40;
19090
+ function sideLabel(side) {
19091
+ const branch = side.branch === void 0 ? "" : `, branch ${side.branch}`;
19092
+ return `${side.path} (trace ${side.traceId}${branch}, ${side.steps.length} steps)`;
19093
+ }
19094
+ function label(step) {
19095
+ const status = step.fields.status;
19096
+ return `${step.kind} ${JSON.stringify(step.name)}${status === "ERROR" ? " ERROR" : ""}`;
19097
+ }
19098
+ function renderRunDiff(report) {
19099
+ const { a, b, diff } = report;
19100
+ const byPairing = { id: 0, position: 0, name: 0 };
19101
+ for (const pair of diff.pairs) byPairing[pair.pairedBy] += 1;
19102
+ const lines = [
19103
+ ...report.kinds ? [`steps limited to kind ${report.kinds.join(", ")}`] : [],
19104
+ `A: ${sideLabel(a)}`,
19105
+ `B: ${sideLabel(b)}`,
19106
+ `paired ${diff.pairs.length} (by id ${byPairing.id}, position ${byPairing.position}, name ${byPairing.name}), only in A ${diff.onlyInA.length}, only in B ${diff.onlyInB.length}, common prefix ${diff.commonPrefixLen}`
19107
+ ];
19108
+ const first = diff.firstDivergence;
19109
+ if (!first) {
19110
+ lines.push(
19111
+ report.caveat ? `same steps, same order \u2014 but this is not agreement on outcome: ${report.caveat}` : "no divergence: the runs agree step for step"
19112
+ );
19113
+ return `${lines.join("\n")}
19114
+ `;
19115
+ }
19116
+ lines.push(`first divergence [${first.kind}] at step ${first.index}: ${first.reason}`);
19117
+ const details = [];
19118
+ for (const pair of diff.pairs) {
19119
+ if (pair.differences.length === 0) continue;
19120
+ const changes = pair.differences.map((d) => `${d.field} ${String(d.a)} -> ${String(d.b)}`).join("; ");
19121
+ details.push(` changed A[${pair.a}]~B[${pair.b}] ${label(a.steps[pair.a])}: ${changes}`);
19122
+ }
19123
+ for (const index of diff.onlyInA) details.push(` only in A A[${index}] ${label(a.steps[index])}`);
19124
+ for (const index of diff.onlyInB) details.push(` only in B B[${index}] ${label(b.steps[index])}`);
19125
+ lines.push(...details.slice(0, TEXT_STEP_LIMIT));
19126
+ if (details.length > TEXT_STEP_LIMIT) {
19127
+ lines.push(` \u2026 ${details.length - TEXT_STEP_LIMIT} more (use --format json for all)`);
19128
+ }
19129
+ return `${lines.join("\n")}
19130
+ `;
19131
+ }
19132
+
18970
19133
  // src/upload-state.ts
18971
19134
  import { mkdir as mkdir8, readFile as readFile17, writeFile as writeFile12 } from "fs/promises";
18972
19135
  import { homedir as homedir12 } from "os";
@@ -19008,7 +19171,7 @@ import { createRequire as createRequire2 } from "module";
19008
19171
  import { hostname as hostname2 } from "os";
19009
19172
  import { basename as basename17, join as join33 } from "path";
19010
19173
  import { hostedClientFromEnv } from "@tangle-network/agent-eval/hosted";
19011
- import { REDACTION_VERSION } from "@tangle-network/agent-eval/traces";
19174
+ import { assessShareSafety as assessShareSafety2, REDACTION_VERSION, redact as redact2, shareAllowed } from "@tangle-network/agent-eval/traces";
19012
19175
  var require2 = createRequire2(import.meta.url);
19013
19176
  var TRACES_VERSION = require2("../package.json").version;
19014
19177
  async function planUpload(opts = {}) {
@@ -19049,7 +19212,7 @@ async function sessionMeta(item) {
19049
19212
  if (item.ref.cwd) meta[ATTR.CWD] = item.ref.cwd;
19050
19213
  const branch = await gitBranch(item.ref.cwd);
19051
19214
  if (branch) meta[ATTR.GIT_BRANCH] = branch;
19052
- return meta;
19215
+ return redact2(meta).value;
19053
19216
  }
19054
19217
  var msToNano = (epochMs2) => (BigInt(epochMs2) * 1000000n).toString();
19055
19218
  function toTraceSpanEvents(spans, rootMeta) {
@@ -19115,6 +19278,7 @@ async function prepareOutbound(plan, opts, uploadedAt) {
19115
19278
  redactionVersion: REDACTION_VERSION
19116
19279
  };
19117
19280
  const items = [];
19281
+ const refused = [];
19118
19282
  for (const item of candidates) {
19119
19283
  const key = uploadKey(item.ref.harness, item.ref.sessionId);
19120
19284
  const previous = plan.state[key];
@@ -19129,13 +19293,18 @@ async function prepareOutbound(plan, opts, uploadedAt) {
19129
19293
  events = eventsAt(eventTimestamp2);
19130
19294
  hash2 = outboundHash(events, identity2);
19131
19295
  }
19296
+ const verdict = assessShareSafety2(events);
19297
+ if (!shareAllowed(verdict)) {
19298
+ refused.push({ harness: item.ref.harness, sessionId: item.ref.sessionId, verdict });
19299
+ continue;
19300
+ }
19132
19301
  items.push({ item, events, hash: hash2 });
19133
19302
  }
19134
- return { items, skipped: plan.items.length - items.length };
19303
+ return { items, skipped: plan.items.length - items.length - refused.length, refused };
19135
19304
  }
19136
19305
  async function executeUpload(plan, opts = {}) {
19137
19306
  const uploadedAt = (/* @__PURE__ */ new Date()).toISOString();
19138
- const { items, skipped } = await prepareOutbound(plan, opts, uploadedAt);
19307
+ const { items, skipped, refused } = await prepareOutbound(plan, opts, uploadedAt);
19139
19308
  const redactionCount = items.reduce((n, { item }) => n + item.redaction.redactionCount, 0);
19140
19309
  if (opts.dryRun) {
19141
19310
  const allSpans = items.flatMap(({ item }) => item.spans);
@@ -19146,7 +19315,8 @@ async function executeUpload(plan, opts = {}) {
19146
19315
  acceptedSpans: 0,
19147
19316
  redactionCount,
19148
19317
  dryRun: true,
19149
- otlpPath: path
19318
+ otlpPath: path,
19319
+ refused
19150
19320
  };
19151
19321
  }
19152
19322
  if (items.length === 0) {
@@ -19155,7 +19325,8 @@ async function executeUpload(plan, opts = {}) {
19155
19325
  skippedSessions: skipped,
19156
19326
  acceptedSpans: 0,
19157
19327
  redactionCount,
19158
- dryRun: false
19328
+ dryRun: false,
19329
+ refused
19159
19330
  };
19160
19331
  }
19161
19332
  const client = opts.backend ?? hostedClientFromEnv();
@@ -19191,12 +19362,128 @@ async function executeUpload(plan, opts = {}) {
19191
19362
  skippedSessions: skipped,
19192
19363
  acceptedSpans,
19193
19364
  redactionCount,
19194
- dryRun: false
19365
+ dryRun: false,
19366
+ refused
19195
19367
  };
19196
19368
  }
19197
19369
 
19370
+ // src/verify-safe.ts
19371
+ import { lstat as lstat4, readdir as readdir21, readFile as readFile19 } from "fs/promises";
19372
+ import { basename as basename18, extname, join as join34, relative as relative7 } from "path";
19373
+ import {
19374
+ assessShareSafety as assessShareSafety3,
19375
+ combineVerdicts as combineVerdicts2
19376
+ } from "@tangle-network/agent-eval/traces";
19377
+ var JSON_LINES = /* @__PURE__ */ new Set([".jsonl", ".ndjson"]);
19378
+ async function verifySafe(target, options = {}) {
19379
+ const profile = options.profile ?? "share";
19380
+ const assess = { profile, knownSecrets: options.knownSecrets };
19381
+ const verdicts = [];
19382
+ const unreadable = [];
19383
+ const files = await listFiles(target, unreadable);
19384
+ for (const file of files) {
19385
+ const label2 = relative7(target, file) || basename18(file);
19386
+ let bytes;
19387
+ try {
19388
+ bytes = await readFile19(file);
19389
+ } catch (error) {
19390
+ unreadable.push(`${label2}: ${error instanceof Error ? error.message : String(error)}`);
19391
+ continue;
19392
+ }
19393
+ const text2 = decodeUtf8(bytes);
19394
+ if (text2 === void 0) {
19395
+ unreadable.push(`${label2}: not UTF-8 text`);
19396
+ continue;
19397
+ }
19398
+ const extension = extname(file).toLowerCase();
19399
+ if (JSON_LINES.has(extension)) {
19400
+ text2.split("\n").forEach((line, index) => {
19401
+ if (!line.trim()) return;
19402
+ const parsed = parseJson(line);
19403
+ if (parsed === void 0) unreadable.push(`${label2}:${index + 1}: not JSON`);
19404
+ else verdicts.push(located(assessShareSafety3(parsed.value, assess), `${label2}:${index + 1}`));
19405
+ });
19406
+ } else if (extension === ".json") {
19407
+ const parsed = parseJson(text2);
19408
+ if (parsed === void 0) unreadable.push(`${label2}: not JSON`);
19409
+ else verdicts.push(located(assessShareSafety3(parsed.value, assess), label2));
19410
+ } else {
19411
+ verdicts.push(located(assessShareSafety3(text2, assess), label2));
19412
+ }
19413
+ }
19414
+ if (files.length === 0 && unreadable.length === 0) unreadable.push(`${target}: no files to check`);
19415
+ return { ...combineVerdicts2(profile, verdicts, unreadable), target, files: files.length };
19416
+ }
19417
+ async function listFiles(target, unreadable) {
19418
+ let stat19;
19419
+ try {
19420
+ stat19 = await lstat4(target);
19421
+ } catch (error) {
19422
+ unreadable.push(`${target}: ${error instanceof Error ? error.message : String(error)}`);
19423
+ return [];
19424
+ }
19425
+ if (stat19.isFile()) return [target];
19426
+ if (!stat19.isDirectory()) {
19427
+ unreadable.push(`${target}: not a regular file or directory`);
19428
+ return [];
19429
+ }
19430
+ const out = [];
19431
+ const walk = async (dir) => {
19432
+ for (const entry of (await readdir21(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
19433
+ const path = join34(dir, entry.name);
19434
+ if (entry.isDirectory()) await walk(path);
19435
+ else if (entry.isFile()) out.push(path);
19436
+ else unreadable.push(`${relative7(target, path)}: not a regular file`);
19437
+ }
19438
+ };
19439
+ await walk(target);
19440
+ return out;
19441
+ }
19442
+ function decodeUtf8(bytes) {
19443
+ if (bytes.includes(0)) return void 0;
19444
+ try {
19445
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
19446
+ } catch {
19447
+ return void 0;
19448
+ }
19449
+ }
19450
+ function parseJson(text2) {
19451
+ try {
19452
+ return { value: JSON.parse(text2) };
19453
+ } catch {
19454
+ return void 0;
19455
+ }
19456
+ }
19457
+ function located(verdict, where) {
19458
+ return {
19459
+ ...verdict,
19460
+ findings: verdict.findings.map((finding2) => ({
19461
+ ...finding2,
19462
+ paths: finding2.paths.map((path) => `${where}#${path}`)
19463
+ })),
19464
+ unreadable: verdict.unreadable.map((entry) => `${where}#${entry}`)
19465
+ };
19466
+ }
19467
+ function verdictExitCode(verdict) {
19468
+ if (verdict.status === "UNKNOWN") return 2;
19469
+ return verdict.status === "UNSAFE" ? 1 : 0;
19470
+ }
19471
+ function formatVerdict(result) {
19472
+ const lines = [`${result.status} ${result.target} (${result.files} file(s), profile ${result.profile})`];
19473
+ for (const finding2 of result.findings) {
19474
+ lines.push(
19475
+ ` ${finding2.severity.padEnd(7)} ${finding2.category}/${finding2.detector} x${finding2.count} ${finding2.paths.join(", ")}`
19476
+ );
19477
+ }
19478
+ for (const entry of result.unreadable) lines.push(` unread ${entry}`);
19479
+ if (result.status === "UNSAFE") lines.push("Refuse to share. Redact with the same profile, then run verify-safe again.");
19480
+ if (result.status === "UNKNOWN") lines.push("Refuse to share: part of the input could not be read, so its safety is unknown.");
19481
+ return lines.join("\n");
19482
+ }
19483
+
19198
19484
  export {
19199
19485
  SOURCE_ATTRIBUTE_PREFIX,
19486
+ toolArgumentsFromAttributes,
19200
19487
  SYNTHESIZED_SPAN_ATTR,
19201
19488
  SYNTHESIZED_SOURCE_ATTR,
19202
19489
  INHERITED_SPAN_ATTR,
@@ -19290,6 +19577,8 @@ export {
19290
19577
  analyzeAdoption,
19291
19578
  planTraceAgenticRoute,
19292
19579
  traceAgenticKinds,
19580
+ writeAnalysisTraceFile,
19581
+ openAgenticTraceStore,
19293
19582
  CITATION_NORMALIZATION_VERSION,
19294
19583
  normalizeAnalystCitations,
19295
19584
  MAX_SHELL_DEPTH,
@@ -19353,6 +19642,7 @@ export {
19353
19642
  collectSessionWorkflow,
19354
19643
  fileSha256,
19355
19644
  collectSessionSelection,
19645
+ tracesVersion,
19356
19646
  listSessionBundleFiles,
19357
19647
  assembleSessionBundle,
19358
19648
  hashSessionBundleFiles,
@@ -19377,9 +19667,8 @@ export {
19377
19667
  hodoscopeAnalyzer,
19378
19668
  httpJsonTransport,
19379
19669
  primeAnalyzer,
19380
- CODING_REDACTION_RULES,
19381
- TRACES_REDACTION_RULES,
19382
19670
  redactSpans,
19671
+ assessSpans,
19383
19672
  applyRedactor,
19384
19673
  exportTraceEvidenceRows,
19385
19674
  exportTraceEvidenceText,
@@ -19456,6 +19745,9 @@ export {
19456
19745
  findSessionTextLeaks,
19457
19746
  classifyEvidenceOnlyPath,
19458
19747
  projectSessionBundle,
19748
+ readRunSide,
19749
+ diffRuns,
19750
+ renderRunDiff,
19459
19751
  uploadKey,
19460
19752
  sessionHash,
19461
19753
  outboundHash,
@@ -19465,5 +19757,8 @@ export {
19465
19757
  planUpload,
19466
19758
  toTraceSpanEvents,
19467
19759
  PartialUploadError,
19468
- executeUpload
19760
+ executeUpload,
19761
+ verifySafe,
19762
+ verdictExitCode,
19763
+ formatVerdict
19469
19764
  };