@dzhechkov/harness-core 0.8.46 → 0.8.48

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.
Files changed (153) hide show
  1. package/.dz-manifest.json +200 -168
  2. package/README.md +130 -2
  3. package/dist/agentdb-index.d.ts.map +1 -1
  4. package/dist/agentdb-index.js +32 -2
  5. package/dist/agentdb-index.js.map +1 -1
  6. package/dist/agentdb-snapshot.d.ts +14 -1
  7. package/dist/agentdb-snapshot.d.ts.map +1 -1
  8. package/dist/agentdb-snapshot.js +100 -4
  9. package/dist/agentdb-snapshot.js.map +1 -1
  10. package/dist/apply-leg.d.ts +9 -1
  11. package/dist/apply-leg.d.ts.map +1 -1
  12. package/dist/apply-leg.js +26 -4
  13. package/dist/apply-leg.js.map +1 -1
  14. package/dist/architecture.d.ts +0 -5
  15. package/dist/architecture.d.ts.map +1 -1
  16. package/dist/architecture.js +25 -2
  17. package/dist/architecture.js.map +1 -1
  18. package/dist/codex-rollouts.d.ts +35 -7
  19. package/dist/codex-rollouts.d.ts.map +1 -1
  20. package/dist/codex-rollouts.js +201 -113
  21. package/dist/codex-rollouts.js.map +1 -1
  22. package/dist/cost-ledger.d.ts +42 -3
  23. package/dist/cost-ledger.d.ts.map +1 -1
  24. package/dist/cost-ledger.js +478 -15
  25. package/dist/cost-ledger.js.map +1 -1
  26. package/dist/feature-adr-routing.d.ts +16 -0
  27. package/dist/feature-adr-routing.d.ts.map +1 -1
  28. package/dist/feature-adr-routing.js +21 -0
  29. package/dist/feature-adr-routing.js.map +1 -1
  30. package/dist/guard.d.ts +7 -0
  31. package/dist/guard.d.ts.map +1 -1
  32. package/dist/guard.js +48 -0
  33. package/dist/guard.js.map +1 -1
  34. package/dist/index.d.ts +11 -6
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +10 -3
  37. package/dist/index.js.map +1 -1
  38. package/dist/loop-plan.d.ts +4 -0
  39. package/dist/loop-plan.d.ts.map +1 -1
  40. package/dist/loop-plan.js +4 -0
  41. package/dist/loop-plan.js.map +1 -1
  42. package/dist/mutation-gate.d.ts +36 -1
  43. package/dist/mutation-gate.d.ts.map +1 -1
  44. package/dist/mutation-gate.js +109 -19
  45. package/dist/mutation-gate.js.map +1 -1
  46. package/dist/npm-homepage.d.ts +64 -0
  47. package/dist/npm-homepage.d.ts.map +1 -0
  48. package/dist/npm-homepage.js +109 -0
  49. package/dist/npm-homepage.js.map +1 -0
  50. package/dist/operations.d.ts +6 -0
  51. package/dist/operations.d.ts.map +1 -1
  52. package/dist/operations.js +150 -24
  53. package/dist/operations.js.map +1 -1
  54. package/dist/pack-inventory.d.ts.map +1 -1
  55. package/dist/pack-inventory.js +6 -1
  56. package/dist/pack-inventory.js.map +1 -1
  57. package/dist/parity.d.ts.map +1 -1
  58. package/dist/parity.js +4 -1
  59. package/dist/parity.js.map +1 -1
  60. package/dist/publish-sibling-drift.d.ts +43 -1
  61. package/dist/publish-sibling-drift.d.ts.map +1 -1
  62. package/dist/publish-sibling-drift.js +269 -13
  63. package/dist/publish-sibling-drift.js.map +1 -1
  64. package/dist/publish.d.ts +5 -1
  65. package/dist/publish.d.ts.map +1 -1
  66. package/dist/publish.js +18 -1
  67. package/dist/publish.js.map +1 -1
  68. package/dist/qe-bridge.d.ts +63 -1
  69. package/dist/qe-bridge.d.ts.map +1 -1
  70. package/dist/qe-bridge.js +82 -0
  71. package/dist/qe-bridge.js.map +1 -1
  72. package/dist/release-package-audit.d.ts +86 -0
  73. package/dist/release-package-audit.d.ts.map +1 -0
  74. package/dist/release-package-audit.js +272 -0
  75. package/dist/release-package-audit.js.map +1 -0
  76. package/dist/release.d.ts +8 -1
  77. package/dist/release.d.ts.map +1 -1
  78. package/dist/release.js +58 -32
  79. package/dist/release.js.map +1 -1
  80. package/dist/round.d.ts +10 -1
  81. package/dist/round.d.ts.map +1 -1
  82. package/dist/round.js +5 -1
  83. package/dist/round.js.map +1 -1
  84. package/dist/run-records.d.ts +11 -0
  85. package/dist/run-records.d.ts.map +1 -1
  86. package/dist/run-records.js +124 -20
  87. package/dist/run-records.js.map +1 -1
  88. package/dist/setup-memory-deps.d.ts +14 -0
  89. package/dist/setup-memory-deps.d.ts.map +1 -0
  90. package/dist/setup-memory-deps.js +210 -0
  91. package/dist/setup-memory-deps.js.map +1 -0
  92. package/dist/setup.d.ts +2 -0
  93. package/dist/setup.d.ts.map +1 -1
  94. package/dist/setup.js +379 -378
  95. package/dist/setup.js.map +1 -1
  96. package/dist/stage-usage.d.ts +248 -0
  97. package/dist/stage-usage.d.ts.map +1 -0
  98. package/dist/stage-usage.js +511 -0
  99. package/dist/stage-usage.js.map +1 -0
  100. package/dist/statusline.d.ts +23 -0
  101. package/dist/statusline.d.ts.map +1 -1
  102. package/dist/statusline.js +167 -1
  103. package/dist/statusline.js.map +1 -1
  104. package/dist/workflow-run-dispatch.d.ts +36 -19
  105. package/dist/workflow-run-dispatch.d.ts.map +1 -1
  106. package/dist/workflow-run-dispatch.js +255 -102
  107. package/dist/workflow-run-dispatch.js.map +1 -1
  108. package/dist/workflow-run.d.ts +19 -3
  109. package/dist/workflow-run.d.ts.map +1 -1
  110. package/dist/workflow-run.js +93 -2
  111. package/dist/workflow-run.js.map +1 -1
  112. package/package.json +3 -3
  113. package/sbom.json +303 -223
  114. package/src/agentdb-index.ts +31 -2
  115. package/src/agentdb-snapshot.ts +105 -4
  116. package/src/apply-leg.ts +26 -4
  117. package/src/architecture.ts +15 -2
  118. package/src/codex-rollouts.ts +155 -147
  119. package/src/cost-ledger.ts +308 -19
  120. package/src/feature-adr-routing.ts +22 -0
  121. package/src/guard.ts +52 -0
  122. package/src/index.ts +15 -3
  123. package/src/loop-plan.ts +8 -0
  124. package/src/mutation-gate.ts +153 -19
  125. package/src/npm-homepage.ts +142 -0
  126. package/src/operations.ts +177 -22
  127. package/src/pack-inventory.ts +6 -1
  128. package/src/parity.ts +4 -1
  129. package/src/publish-sibling-drift.ts +214 -11
  130. package/src/publish.ts +20 -1
  131. package/src/qe-bridge.ts +86 -1
  132. package/src/release-package-audit.ts +264 -0
  133. package/src/release.ts +55 -22
  134. package/src/round.ts +8 -0
  135. package/src/run-records.ts +101 -22
  136. package/src/setup-memory-deps.ts +166 -0
  137. package/src/setup.ts +95 -98
  138. package/src/stage-usage.ts +385 -0
  139. package/src/statusline.ts +149 -1
  140. package/src/workflow-run-dispatch.ts +197 -93
  141. package/src/workflow-run.ts +101 -5
  142. package/dist/ledger-cost-fill.d.ts +0 -58
  143. package/dist/ledger-cost-fill.d.ts.map +0 -1
  144. package/dist/ledger-cost-fill.js +0 -78
  145. package/dist/ledger-cost-fill.js.map +0 -1
  146. package/dist/retro.d.ts +0 -131
  147. package/dist/retro.d.ts.map +0 -1
  148. package/dist/retro.js +0 -207
  149. package/dist/retro.js.map +0 -1
  150. package/dist/sbom.d.ts +0 -42
  151. package/dist/sbom.d.ts.map +0 -1
  152. package/dist/sbom.js +0 -120
  153. package/dist/sbom.js.map +0 -1
@@ -47,10 +47,16 @@
47
47
  *
48
48
  * @packageDocumentation
49
49
  */
50
- import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
51
- import { dirname, join } from 'node:path';
52
- import { hasKnownPricing, usageCost } from './cost-scoring.js';
50
+ import { closeSync, constants, fstatSync, openSync, readSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
51
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
52
+ import { MODEL_PRICES, hasKnownPricing, usageCost } from './cost-scoring.js';
53
53
  import { CANONICAL_STAGES, canonicalStage } from './feature-adr-stage-canon.js';
54
+ import { resolveLedgerModelProvenance } from './run-records.js';
55
+ import { buildStageUsageReport } from './stage-usage.js';
56
+ import { parseCodexRollout } from './codex-rollouts.js';
57
+ import { fnv1a64 } from './feature-adr-checkpoints.js';
58
+ import { createHash } from 'node:crypto';
59
+ import { parseTrace } from './loop-trace.js';
54
60
  import { claudeProjectsRoot, rawTokenMixOf, weightedTokensOf } from './usage.js';
55
61
  // ── Scope + vocabulary ──────────────────────────────────────
56
62
  /** The one sentence that states what the ledger is and is not. Printed by EVERY surface (ADR-003). */
@@ -330,6 +336,7 @@ export function buildCostLedger(input) {
330
336
  startedAtMs: null,
331
337
  endedAtMs: null,
332
338
  costUsd: 0,
339
+ familyCostUsd: null,
333
340
  pricingKnown: true,
334
341
  };
335
342
  buckets.set(bucketKey, b);
@@ -342,7 +349,9 @@ export function buildCostLedger(input) {
342
349
  const end = stage.durationMs === null ? stage.startedAtMs : stage.startedAtMs + stage.durationMs;
343
350
  b.endedAtMs = b.endedAtMs === null ? end : Math.max(b.endedAtMs, end);
344
351
  }
345
- if (!hasKnownPricing(stage.model))
352
+ const rateKey = Object.keys(MODEL_PRICES).filter((key) => stage.model.toLowerCase().replace(/^[a-z0-9-]+\//, '').startsWith(key)).sort((a, b) => b.length - a.length)[0];
353
+ const exactPrice = rateKey === stage.model.toLowerCase() && !rateKey?.includes('claude');
354
+ if (!exactPrice)
346
355
  b.pricingKnown = false;
347
356
  // Price per AGENT, using that agent's own model, then aggregate — a `mixed` label must not be
348
357
  // priced at one arbitrary model's rate.
@@ -371,8 +380,13 @@ export function buildCostLedger(input) {
371
380
  completionTokens: mix.completionTokens + s.output,
372
381
  };
373
382
  }
374
- const cost = usageCost(mix, stage.model);
375
- b.costUsd += Number.isFinite(cost) && cost > 0 ? cost : 0;
383
+ const cost = hasKnownPricing(stage.model) ? usageCost(mix, stage.model) : null;
384
+ if (cost !== null && Number.isFinite(cost)) {
385
+ if (exactPrice)
386
+ b.costUsd += cost;
387
+ else
388
+ b.familyCostUsd = (b.familyCostUsd ?? 0) + cost;
389
+ }
376
390
  }
377
391
  // accountedTokens — the DEDUPED union of stage-claimed samples, so a double-claim inflates
378
392
  // `stageTokensSum` without inflating this. That difference IS `doubleAttributedTokens`.
@@ -416,7 +430,15 @@ export function buildCostLedger(input) {
416
430
  tokensCacheRead,
417
431
  tokensOut,
418
432
  weightedTokens: b.sum,
419
- costUsd: b.costUsd,
433
+ costUsd: b.pricingKnown ? b.costUsd : null,
434
+ knownEstimatedCostUsd: b.costUsd,
435
+ familyEstimatedCostUsd: b.pricingKnown ? null : b.familyCostUsd,
436
+ pricingProvenance: models.map((model) => {
437
+ const normalized = model.toLowerCase().replace(/^[a-z0-9-]+\//, '');
438
+ const tableKey = Object.keys(MODEL_PRICES).filter((key) => normalized.startsWith(key)).sort((a, b) => b.length - a.length)[0] ?? null;
439
+ return { model, tableKey, matchKind: tableKey === null ? 'unknown' : tableKey === normalized && !tableKey.includes('claude') ? 'exact' : 'family-estimate',
440
+ source: 'MODEL_PRICES static snapshot', fingerprint: fnv1a64(JSON.stringify(MODEL_PRICES)), current: false, billed: false };
441
+ }),
420
442
  pricingKnown: b.pricingKnown,
421
443
  startedTs: isoOrNull(b.startedAtMs),
422
444
  endedTs: isoOrNull(b.endedAtMs),
@@ -543,9 +565,8 @@ export function buildCostLedger(input) {
543
565
  : hasOrphan
544
566
  ? 'INCOMPLETE_INVENTORY'
545
567
  : 'BALANCED';
546
- let totalCostUsd = 0;
547
- for (const r of rows)
548
- totalCostUsd += r.costUsd;
568
+ const knownEstimatedCostUsd = rows.reduce((n, r) => n + (r.knownEstimatedCostUsd ?? 0), 0);
569
+ const totalCostUsd = rows.every((r) => r.costUsd !== null) ? knownEstimatedCostUsd : null;
549
570
  const fallbackModels = [...new Set(record.stages.filter((s) => !hasKnownPricing(s.model)).map((s) => s.model))].sort();
550
571
  const byCanonicalStage = {};
551
572
  for (const k of [...CANONICAL_STAGES, 'unknown', 'unattributed'])
@@ -579,6 +600,7 @@ export function buildCostLedger(input) {
579
600
  },
580
601
  recordTotalTokens: record.recordTotalTokens,
581
602
  totalCostUsd,
603
+ knownEstimatedCostUsd,
582
604
  pricingFallbackModels: fallbackModels,
583
605
  estimated: true,
584
606
  scope: COST_LEDGER_SCOPE,
@@ -650,7 +672,7 @@ export function stageCostAggregates(reports) {
650
672
  acc.set(key, a);
651
673
  }
652
674
  a.total += row.weightedTokens;
653
- a.cost += row.costUsd;
675
+ a.cost = a.cost === null || row.costUsd === null ? null : a.cost + row.costUsd;
654
676
  a.runs.add(row.runId);
655
677
  }
656
678
  }
@@ -663,7 +685,7 @@ export function stageCostAggregates(reports) {
663
685
  avgTokens: runs > 0 ? Math.round(a.total / runs) : 0,
664
686
  runs,
665
687
  totalTokens: a.total,
666
- avgCostUsd: runs > 0 ? a.cost / runs : 0,
688
+ avgCostUsd: a.cost === null ? null : runs > 0 ? a.cost / runs : 0,
667
689
  });
668
690
  }
669
691
  out.sort((x, y) => y.avgTokens - x.avgTokens || x.stage.localeCompare(y.stage));
@@ -676,6 +698,8 @@ function fmt(n) {
676
698
  return Math.round(n).toLocaleString('en-US');
677
699
  }
678
700
  function usd(n) {
701
+ if (n === null)
702
+ return 'unavailable';
679
703
  if (!Number.isFinite(n) || n <= 0)
680
704
  return '$0.00';
681
705
  return '$' + n.toFixed(n < 1 ? 4 : 2);
@@ -746,7 +770,7 @@ export function renderCostLedger(report) {
746
770
  lines.push(` note: the run record's own totalTokens is ${fmt(report.recordTotalTokens)} — a RAW unweighted cached sum of the same per-agent list, reported for traceability, NOT the invariant's right-hand side`);
747
771
  }
748
772
  if (report.pricingFallbackModels.length > 0) {
749
- lines.push(` note: ~USD marked * uses sonnet-class FALLBACK pricing for: ${report.pricingFallbackModels.join(', ')}`);
773
+ lines.push(` note: primary ~USD unavailable; FALLBACK pricing is not used for: ${report.pricingFallbackModels.join(', ')}`);
750
774
  }
751
775
  lines.push(` scope: ${COST_LEDGER_SCOPE}`);
752
776
  return lines.join('\n');
@@ -899,7 +923,7 @@ export function deriveCostLedger(opts = {}) {
899
923
  return null;
900
924
  if (opts.slug !== undefined && !SLUG_PATTERN.test(opts.slug))
901
925
  return null;
902
- const runs = listCostLedgerRuns(opts);
926
+ const runs = listCostLedgerRuns({ ...opts, ...(opts.projectRoot !== undefined && opts.projectDir === undefined ? { projectDir: resolve(opts.projectRoot).replace(/[\\/]/g, '-') } : {}) }).filter((ref) => opts.projectRoot === undefined || relative(opts.projectsRoot ?? claudeProjectsRoot(), ref.recordPath).split(sep)[0] === resolve(opts.projectRoot).replace(/[\\/]/g, '-'));
903
927
  const ref = opts.runId !== undefined
904
928
  ? runs.find((r) => r.runId === opts.runId)
905
929
  : opts.slug !== undefined
@@ -966,7 +990,7 @@ export function deriveCostLedger(opts = {}) {
966
990
  perAgent.set(agentId, samples);
967
991
  }
968
992
  }
969
- return buildCostLedger({
993
+ const report = buildCostLedger({
970
994
  record,
971
995
  stageSamples: [...perAgent.entries()].map(([agentId, samples]) => ({ agentId, samples })),
972
996
  runSamples,
@@ -975,6 +999,8 @@ export function deriveCostLedger(opts = {}) {
975
999
  ...(listingTruncated ? { transcriptListingTruncated: true } : {}),
976
1000
  ...(opts.epsilon !== undefined ? { epsilon: opts.epsilon } : {}),
977
1001
  });
1002
+ return { ...report, authoritativeEvidence: { roots: [opts.projectsRoot ?? claudeProjectsRoot(), dirname(dirname(dirname(ref.recordPath))), ref.transcriptDir],
1003
+ files: [ref.recordPath, ...files.map((file) => join(ref.transcriptDir, file)), ...record.stages.map((stage) => join(ref.transcriptDir, 'agent-' + stage.agentId + '.jsonl'))] } };
978
1004
  }
979
1005
  catch {
980
1006
  return null; // never-throw contract
@@ -1024,4 +1050,441 @@ export function writeCostLedgerJsonl(path, report) {
1024
1050
  return false;
1025
1051
  }
1026
1052
  }
1053
+ // Capture contract: ordered named fields, with absent distinct from explicit null.
1054
+ // Kept private at producer/reader boundaries; both use this exact sha256 representation.
1055
+ function capturedPayload(evidence) {
1056
+ const value = (v) => v === undefined ? { absent: true } : v;
1057
+ const receipts = Array.isArray(evidence['receipts']) ? evidence['receipts'] : [];
1058
+ return JSON.stringify([
1059
+ ...['schema', 'sessionId', 'turnId', 'sourcePath', 'matchBasis', 'capturedFrom', 'capturedTo', 'model', 'cwd', 'reportedTotalBasis', 'inputCacheSemantics'].map((k) => [k, value(evidence[k])]),
1060
+ ['owner', ...['runId', 'taskId', 'stage', 'attempt', 'role'].map((k) => [k, value(evidence['owner']?.[k])])],
1061
+ receipts.map((raw) => {
1062
+ const r = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
1063
+ const t = r['totals'];
1064
+ return [...['key', 'responseId', 'turnId', 'turnIndex', 'timestamp', 'payloadDigest', 'source'].map((k) => [k, value(r[k])]),
1065
+ ['totals', ...['input', 'output', 'cachedInput', 'cachedWrite', 'reasoning', 'total'].map((k) => [k, value(t?.[k])])]];
1066
+ }),
1067
+ ]);
1068
+ }
1069
+ const stageObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
1070
+ function stageReadText(path, allowedRoot, maxBytes = 64 * 1024 * 1024) {
1071
+ let fd;
1072
+ try {
1073
+ const root = realpathSync(allowedRoot);
1074
+ const abs = resolve(path);
1075
+ const actual = realpathSync(abs);
1076
+ const stat = lstatSync(abs);
1077
+ if (!stat.isFile() || stat.isSymbolicLink() || (actual !== root && !actual.startsWith(root + sep)))
1078
+ return { text: null, diagnostic: 'source-outside-root-or-nonregular' };
1079
+ let ancestor = dirname(abs);
1080
+ while (ancestor !== root && ancestor !== dirname(ancestor)) {
1081
+ if (lstatSync(ancestor).isSymbolicLink())
1082
+ return { text: null, diagnostic: 'source-symlink' };
1083
+ ancestor = dirname(ancestor);
1084
+ }
1085
+ fd = openSync(abs, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
1086
+ const opened = fstatSync(fd);
1087
+ if (!opened.isFile() || opened.ino !== stat.ino || opened.dev !== stat.dev)
1088
+ return { text: null, diagnostic: 'source-changed-during-read' };
1089
+ const chunks = [];
1090
+ let bytes = 0;
1091
+ while (true) {
1092
+ const chunk = Buffer.alloc(Math.min(65536, maxBytes + 1 - bytes));
1093
+ const n = readSync(fd, chunk, 0, chunk.length, null);
1094
+ if (!n)
1095
+ break;
1096
+ bytes += n;
1097
+ if (bytes > maxBytes)
1098
+ return { text: null, diagnostic: 'source-input-too-large' };
1099
+ chunks.push(chunk.subarray(0, n));
1100
+ }
1101
+ return { text: new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks)), diagnostic: null };
1102
+ }
1103
+ catch {
1104
+ return { text: null, diagnostic: 'missing-or-unreadable-source' };
1105
+ }
1106
+ finally {
1107
+ if (fd !== undefined)
1108
+ closeSync(fd);
1109
+ }
1110
+ }
1111
+ function stageReadRows(path, root, maxBytes, maxRecords = 100000) {
1112
+ const read = stageReadText(path, root, maxBytes);
1113
+ const diagnostics = [];
1114
+ const rows = [];
1115
+ if (read.diagnostic)
1116
+ diagnostics.push(read.diagnostic);
1117
+ let scanned = 0;
1118
+ for (const line of (read.text ?? '').split('\n')) {
1119
+ if (!line.trim())
1120
+ continue;
1121
+ if (++scanned > maxRecords) {
1122
+ diagnostics.push('inventory-truncated');
1123
+ break;
1124
+ }
1125
+ try {
1126
+ const value = JSON.parse(line);
1127
+ if (stageObject(value))
1128
+ rows.push(value);
1129
+ else
1130
+ diagnostics.push('malformed-record');
1131
+ }
1132
+ catch {
1133
+ diagnostics.push('malformed-record');
1134
+ }
1135
+ }
1136
+ return { rows, diagnostics, fingerprint: read.text === null ? null : createHash('sha256').update(read.text).digest('hex') };
1137
+ }
1138
+ /** Scoped source selection. Explicit project/run never falls through to an unrelated global run. */
1139
+ export function deriveStageUsageReport(opts) {
1140
+ const project = resolve(opts.projectRoot);
1141
+ const source = opts.source ?? 'auto';
1142
+ const sourceRoot = resolve(opts.codexSessionsRoot ?? join(process.env['HOME'] ?? '', '.codex/sessions'));
1143
+ const roots = [join(project, '.dz'), sourceRoot, resolve(opts.projectsRoot ?? claudeProjectsRoot()), join(resolve(opts.projectsRoot ?? claudeProjectsRoot()), project.replace(/[\\/]/g, '-'))];
1144
+ const files = [];
1145
+ if (opts.runDir !== undefined)
1146
+ roots.push(resolve(project, opts.runDir));
1147
+ if (source === 'claude-transcript')
1148
+ roots.push(resolve(opts.projectsRoot ?? claudeProjectsRoot()));
1149
+ const build = (input) => ({ ...buildStageUsageReport(input), authoritativeEvidence: { roots, files } });
1150
+ const insufficient = (reason) => build({ sourceKind: source, sourcePath: project, runId: opts.runId ?? null, rows: [], diagnostics: [reason] });
1151
+ if (!['auto', 'workflow-budget', 'fa-ledger', 'claude-transcript'].includes(source))
1152
+ return insufficient('unknown-source');
1153
+ const faPath = join(project, '.dz/feature-adr/run-cost-ledger.jsonl');
1154
+ files.push(faPath);
1155
+ const fa = stageReadRows(faPath, project, opts.maxBytes, opts.maxRecords);
1156
+ const faMatches = fa.rows.filter((row) => (opts.runId === undefined || row['runId'] === opts.runId) && (opts.slug === undefined || row['slug'] === opts.slug));
1157
+ const faIds = [...new Set(faMatches.map((row) => typeof row['runId'] === 'string' ? row['runId'] : '').filter(Boolean))];
1158
+ const projections = faMatches.filter((row) => row['summary'] === true && row['sourceProjection'] === 'workflow-budget');
1159
+ const projectionDiagnostics = [];
1160
+ const projectedIds = new Set();
1161
+ const safeRunId = (value) => typeof value === 'string' && /^[A-Za-z0-9_.-]{1,128}$/.test(value) && value !== '.' && value !== '..';
1162
+ for (const row of projections) {
1163
+ if (!safeRunId(row['workflowRunId']) || row['runId'] !== row['workflowRunId'] || (row['projectRoot'] !== undefined && (typeof row['projectRoot'] !== 'string' || resolve(row['projectRoot']) !== project))) {
1164
+ projectionDiagnostics.push('inventory-workflow-projection-identity-invalid');
1165
+ continue;
1166
+ }
1167
+ projectedIds.add(row['workflowRunId']);
1168
+ }
1169
+ const projectedRunId = projectedIds.size === 1 ? [...projectedIds][0] : null;
1170
+ const traceRoot = join(project, '.dz/loop-trace');
1171
+ let wfDirs = [];
1172
+ if (opts.runDir !== undefined)
1173
+ wfDirs = [resolve(project, opts.runDir)];
1174
+ else if (opts.runId !== undefined && safeRunId(opts.runId))
1175
+ wfDirs = [join(traceRoot, opts.runId)].filter((dir) => existsSync(join(dir, 'budget.jsonl')));
1176
+ else if (opts.slug !== undefined)
1177
+ wfDirs = [...projectedIds].map((id) => join(traceRoot, id));
1178
+ else if (opts.runId === undefined) {
1179
+ try {
1180
+ wfDirs = readdirSync(traceRoot).filter(safeRunId).map((name) => join(traceRoot, name)).filter((dir) => existsSync(join(dir, 'budget.jsonl')));
1181
+ }
1182
+ catch { /* no selected Wf inventory */ }
1183
+ }
1184
+ roots.push(...wfDirs);
1185
+ for (const dir of wfDirs)
1186
+ files.push(...['budget.jsonl', 'trace.jsonl', 'run-state.json'].map((name) => join(dir, name)));
1187
+ // Scoped native census, preserving latest-within-source semantics while refusing unresolved ties.
1188
+ const claudeRoot = resolve(opts.projectsRoot ?? claudeProjectsRoot());
1189
+ const projectDir = project.replace(/[\\/]/g, '-');
1190
+ const claudeRefs = (opts.runId !== undefined && !RUN_ID_PATTERN.test(opts.runId)) || (opts.slug !== undefined && !SLUG_PATTERN.test(opts.slug)) ? []
1191
+ : listCostLedgerRuns({ projectsRoot: claudeRoot, projectDir }).filter((ref) => (opts.runId === undefined || ref.runId === opts.runId) && (opts.slug === undefined || ref.slug === opts.slug));
1192
+ roots.push(claudeRoot, join(claudeRoot, projectDir));
1193
+ for (const ref of claudeRefs) {
1194
+ roots.push(ref.transcriptDir);
1195
+ files.push(ref.recordPath, ...safeListDir(ref.transcriptDir).filter((f) => f.endsWith('.jsonl')).slice(0, MAX_RUN_TRANSCRIPT_FILES).map((f) => join(ref.transcriptDir, f)));
1196
+ }
1197
+ const claudeAmbiguous = claudeRefs.length > 1 && claudeRefs[0].startedAtMs === claudeRefs[1].startedAtMs;
1198
+ const faIndependent = faMatches.some((row) => !(!stageObject(row['usageEvidence']) && (row['summary'] === true || ['full', 'round', 'control', 'publish'].includes(String(row['stage'])))));
1199
+ const authorities = Number(wfDirs.length > 0) + Number(faIndependent) + Number(claudeRefs.length > 0);
1200
+ if (source === 'auto' && (authorities > 1 || wfDirs.length > 1 || (faIndependent && faIds.length > 1) || claudeAmbiguous))
1201
+ return insufficient('source-selection-ambiguous');
1202
+ if (source === 'auto' && projectionDiagnostics.length)
1203
+ return insufficient(projectionDiagnostics[0]);
1204
+ const chosen = source === 'auto' ? wfDirs.length === 1 ? 'workflow-budget' : faIndependent ? 'fa-ledger' : claudeRefs.length ? 'claude-transcript' : projections.length ? 'fa-ledger' : 'none' : source;
1205
+ if (chosen === 'workflow-budget') {
1206
+ if (opts.slug !== undefined && opts.runId === undefined && projectedRunId === null)
1207
+ return insufficient(projectionDiagnostics[0] ?? 'workflow-source-missing-or-ambiguous');
1208
+ if (wfDirs.length !== 1)
1209
+ return insufficient('workflow-source-missing-or-ambiguous');
1210
+ const dir = wfDirs[0];
1211
+ const allowed = opts.runDir === undefined ? project : dir;
1212
+ const budget = stageReadRows(join(dir, 'budget.jsonl'), allowed, opts.maxBytes, opts.maxRecords);
1213
+ const trace = stageReadRows(join(dir, 'trace.jsonl'), allowed, opts.maxBytes, opts.maxRecords);
1214
+ const stateRead = stageReadText(join(dir, 'run-state.json'), allowed, opts.maxBytes);
1215
+ let state = null;
1216
+ try {
1217
+ const value = JSON.parse(stateRead.text ?? 'null');
1218
+ if (stageObject(value))
1219
+ state = value;
1220
+ }
1221
+ catch { /* named below */ }
1222
+ const runId = opts.runId ?? projectedRunId ?? (typeof state?.['runId'] === 'string' ? state['runId'] : null);
1223
+ const diagnostics = [...budget.diagnostics, ...trace.diagnostics, ...projectionDiagnostics];
1224
+ if (!state || state['runId'] !== runId)
1225
+ diagnostics.push('inventory-run-state-unavailable-or-foreign');
1226
+ if (state && state['traceSha256'] == null)
1227
+ diagnostics.push('inventory-trace-binding-unavailable');
1228
+ else if (state && state['traceSha256'] !== trace.fingerprint)
1229
+ diagnostics.push('inventory-trace-binding-mismatch');
1230
+ const validatedTrace = parseTrace(trace.rows.map((row) => JSON.stringify(row)).join('\n'));
1231
+ for (const row of projections)
1232
+ if (row['workflowRunId'] !== runId || row['runId'] !== runId || (row['planDigest'] !== undefined && (typeof row['planDigest'] !== 'string' || row['planDigest'] !== state?.['planDigest'] || row['planDigest'] !== validatedTrace.planDigest)))
1233
+ diagnostics.push('inventory-workflow-projection-identity-mismatch');
1234
+ if (validatedTrace.parseErrors.length || validatedTrace.openConflict)
1235
+ diagnostics.push('inventory-trace-invalid');
1236
+ const rows = budget.rows.filter((row) => {
1237
+ if (row['schema'] !== 'wf-budget-1' || (row['kind'] !== 'stage' && row['kind'] !== 'probe')) {
1238
+ diagnostics.push('malformed-budget-schema');
1239
+ return false;
1240
+ }
1241
+ if (row['projectRoot'] !== undefined && resolve(String(row['projectRoot'])) !== project) {
1242
+ diagnostics.push('foreign-project-root');
1243
+ return false;
1244
+ }
1245
+ return true;
1246
+ });
1247
+ const expected = validatedTrace.events.filter((row) => row.event === 'dispatched' && row.runId === runId).map((row) => ({ ...row, dispatchSeq: row.seq, evidenceKey: JSON.stringify(['wf-dispatch', row.runId, row.seq]) }));
1248
+ if (trace.rows.some((row) => row['runId'] !== runId))
1249
+ diagnostics.push('foreign-trace-run');
1250
+ return build({ sourceKind: 'workflow-budget', sourcePath: dir, runId, rows,
1251
+ ...(trace.diagnostics.length === 0 ? { expected } : {}), diagnostics });
1252
+ }
1253
+ if (chosen === 'fa-ledger') {
1254
+ if (opts.runDir !== undefined)
1255
+ return insufficient('source-selection-conflicting-run-dir');
1256
+ if (opts.runId === undefined && faIds.length !== 1)
1257
+ return insufficient('fa-run-selection-ambiguous-or-unidentified');
1258
+ const runId = opts.runId ?? faIds[0];
1259
+ const selected = faMatches.filter((row) => row['runId'] === runId);
1260
+ const diagnostics = [...fa.diagnostics];
1261
+ const sourceDiagnostics = [];
1262
+ const rows = [];
1263
+ const expected = [];
1264
+ const witnesses = [];
1265
+ const moneyObservations = [];
1266
+ let independent = true;
1267
+ for (const row of selected) {
1268
+ const identity = resolveLedgerModelProvenance(row);
1269
+ const evidence = stageObject(row['usageEvidence']) ? row['usageEvidence'] : null;
1270
+ if (!evidence && (row['summary'] === true || ['full', 'round', 'control', 'publish'].includes(String(row['stage']))))
1271
+ continue;
1272
+ const suppliedMoney = stageObject(row['reportedCostObservation']) ? row['reportedCostObservation'] : null;
1273
+ const moneyScope = evidence && Array.isArray(evidence['receipts']) ? JSON.stringify([evidence['sessionId'], evidence['turnId'], evidence['receipts'].filter(stageObject).map((r) => r['key']).sort()]) : null;
1274
+ moneyObservations.push({ id: suppliedMoney?.['id'] ?? (moneyScope === null ? null : 'captured-scope:' + fnv1a64(moneyScope)),
1275
+ scope: suppliedMoney?.['scope'] ?? moneyScope, basis: suppliedMoney?.['basis'] ?? 'caller-reported-captured-scope', runId: row['runId'], amount: row['reportedCostUsd'] ?? null });
1276
+ if (!evidence || !Array.isArray(evidence['receipts']) || evidence['schema'] !== 'codex-rollout-scope-1') {
1277
+ rows.push(row);
1278
+ independent = false;
1279
+ diagnostics.push('source-scope-unavailable');
1280
+ continue;
1281
+ }
1282
+ for (const field of ['capturedFrom', 'capturedTo'])
1283
+ if (evidence[field] != null && (typeof evidence[field] !== 'string' || !Number.isFinite(Date.parse(evidence[field]))))
1284
+ sourceDiagnostics.push('invalid-captured-window:' + field);
1285
+ if (!['codex', 'openai'].includes(identity.family ?? ''))
1286
+ sourceDiagnostics.push('captured-source-model-family-conflict');
1287
+ if (identity.model === null || identity.diagnostics.length)
1288
+ sourceDiagnostics.push(...identity.diagnostics.map((d) => 'captured-' + d));
1289
+ const captured = evidence['receipts'].filter(stageObject);
1290
+ if (captured.some((r) => !stageObject(r['totals'])))
1291
+ sourceDiagnostics.push('invalid-captured-totals');
1292
+ if (evidence['captureSha256'] !== undefined) {
1293
+ if (createHash('sha256').update(capturedPayload(evidence)).digest('hex') !== evidence['captureSha256'])
1294
+ sourceDiagnostics.push('captured-payload-sha256-mismatch');
1295
+ const owner = stageObject(evidence['owner']) ? evidence['owner'] : {};
1296
+ for (const key of ['runId', 'taskId', 'stage', 'attempt', 'role'])
1297
+ if (owner[key] !== (row[key] ?? null))
1298
+ sourceDiagnostics.push('captured-owner-mismatch:' + key);
1299
+ if (evidence['model'] !== identity.model || evidence['cwd'] !== project || evidence['reportedTotalBasis'] !== row['reportedTotalBasis'] || evidence['inputCacheSemantics'] !== row['inputCacheSemantics'])
1300
+ sourceDiagnostics.push('captured-scope-mismatch');
1301
+ }
1302
+ if (fnv1a64(JSON.stringify(captured.map((receipt) => [receipt['key'], receipt['payloadDigest']]))) !== evidence['payloadDigest'])
1303
+ sourceDiagnostics.push('captured-scope-fingerprint-mismatch');
1304
+ if (captured.length !== evidence['receipts'].length) {
1305
+ sourceDiagnostics.push('invalid-captured-scope');
1306
+ independent = false;
1307
+ }
1308
+ const sourcePath = typeof evidence['sourcePath'] === 'string' ? evidence['sourcePath'] : '';
1309
+ if (sourcePath)
1310
+ files.push(resolve(sourcePath));
1311
+ const original = stageReadText(sourcePath, sourceRoot, opts.maxBytes);
1312
+ const parsed = original.text === null ? null : parseCodexRollout(original.text, sourcePath);
1313
+ if (evidence['matchBasis'] !== 'exact') {
1314
+ independent = false;
1315
+ diagnostics.push('legacy-window-assurance');
1316
+ }
1317
+ let scopedTotal = 0;
1318
+ let allKnown = true;
1319
+ const scopeAssociations = new Set();
1320
+ const matchedSourceKeys = new Set();
1321
+ let maxSourceRecord = -1;
1322
+ const scopedDimensions = { tokensIn: 0, tokensOut: 0, tokensCacheRead: 0, tokensCacheWrite: 0, tokensReasoning: 0 };
1323
+ for (const receipt of captured) {
1324
+ const key = typeof receipt['key'] === 'string' ? receipt['key'] : null;
1325
+ const totals = stageObject(receipt['totals']) ? receipt['totals'] : {};
1326
+ const claim = { ...row, reportedCostUsd: null, evidenceKey: key, tokensTotal: totals['total'], tokensIn: totals['input'], tokensOut: totals['output'],
1327
+ tokensCacheRead: totals['cachedInput'], tokensCacheWrite: totals['cachedWrite'], tokensReasoning: totals['reasoning'],
1328
+ reportedTotalBasis: 'raw-inclusive', inputCacheSemantics: 'includes-cache-read-write' };
1329
+ rows.push(claim);
1330
+ expected.push({ evidenceKey: key });
1331
+ if (!parsed || 'error' in parsed || parsed.id !== evidence['sessionId']) {
1332
+ independent = false;
1333
+ diagnostics.push('source-witness-unavailable');
1334
+ continue;
1335
+ }
1336
+ const found = parsed.receipts?.find((r) => JSON.stringify([parsed.id, r.key]) === key);
1337
+ if (!found) {
1338
+ sourceDiagnostics.push('captured-source-receipt-missing-or-foreign');
1339
+ independent = false;
1340
+ continue;
1341
+ }
1342
+ matchedSourceKeys.add(found.key);
1343
+ maxSourceRecord = Math.max(maxSourceRecord, found.sourceRecord ?? -1);
1344
+ for (const diagnostic of found.diagnostics ?? [])
1345
+ sourceDiagnostics.push('source-receipt:' + diagnostic);
1346
+ for (const field of ['input', 'output', 'cachedInput', 'cachedWrite', 'reasoning', 'total'])
1347
+ if (totals[field] !== found.totals[field])
1348
+ sourceDiagnostics.push('captured-source-dimension-mismatch:' + field);
1349
+ for (const field of ['responseId', 'turnId', 'timestamp', 'source'])
1350
+ if (receipt[field] !== found[field])
1351
+ sourceDiagnostics.push('captured-source-identity-mismatch:' + field);
1352
+ // Association refers to the original independently parsed array, never a filtered view.
1353
+ let turn;
1354
+ let sourceTurnIndex = null;
1355
+ let sessionScope = false;
1356
+ if (found.turnIndex != null) {
1357
+ if (Number.isSafeInteger(found.turnIndex) && found.turnIndex >= 0 && found.turnIndex < parsed.turns.length) {
1358
+ sourceTurnIndex = found.turnIndex;
1359
+ turn = parsed.turns[sourceTurnIndex];
1360
+ }
1361
+ else
1362
+ sourceDiagnostics.push('source-turn-index-invalid');
1363
+ }
1364
+ else if (found.turnId !== null) {
1365
+ const matches = parsed.turns.map((t, index) => ({ t, index })).filter(({ t }) => t.turnId === found.turnId);
1366
+ if (matches.length === 1) {
1367
+ sourceTurnIndex = matches[0].index;
1368
+ turn = matches[0].t;
1369
+ }
1370
+ else
1371
+ sourceDiagnostics.push('source-turn-identity-missing-or-ambiguous');
1372
+ }
1373
+ else if (parsed.granularity === 'session' && parsed.turns.length === 0 && evidence['turnId'] == null)
1374
+ sessionScope = true;
1375
+ if (!turn && !sessionScope) {
1376
+ sourceDiagnostics.push('source-turn-association-unavailable');
1377
+ independent = false;
1378
+ }
1379
+ if (turn?.turnId != null && (parsed.turns.filter((t) => t.turnId === turn.turnId).length !== 1 || (found.turnId !== null && found.turnId !== turn.turnId)))
1380
+ sourceDiagnostics.push('source-turn-identity-conflict');
1381
+ if (evidence['turnId'] != null && (evidence['turnId'] !== found.turnId || (turn?.turnId != null && evidence['turnId'] !== turn.turnId)))
1382
+ sourceDiagnostics.push('captured-turn-identity-conflict');
1383
+ const explicitLegacyAssociation = receipt['turnIndex'] == null && receipt['turnId'] != null && receipt['turnId'] === found.turnId
1384
+ && turn?.turnId === found.turnId && parsed.turns.filter((t) => t.turnId === found.turnId).length === 1;
1385
+ if (receipt['turnIndex'] !== found.turnIndex && !explicitLegacyAssociation && !(sessionScope && receipt['turnIndex'] == null && found.turnIndex == null))
1386
+ sourceDiagnostics.push('captured-source-identity-mismatch:turnIndex');
1387
+ if (turn || sessionScope) {
1388
+ scopeAssociations.add(turn ? 'turn:' + sourceTurnIndex : 'session');
1389
+ const authority = turn ?? parsed;
1390
+ if (evidence['capturedFrom'] !== authority.startedAt)
1391
+ sourceDiagnostics.push('captured-source-start-scope-mismatch');
1392
+ if (identity.model !== authority.model || authority.cwd === null || resolve(authority.cwd) !== project
1393
+ || row['reportedTotalBasis'] !== 'raw-inclusive' || row['inputCacheSemantics'] !== 'includes-cache-read-write')
1394
+ sourceDiagnostics.push('captured-source-scope-mismatch');
1395
+ }
1396
+ if (found.timestamp !== null && ((typeof evidence['capturedFrom'] === 'string' && Date.parse(found.timestamp) < Date.parse(evidence['capturedFrom'])) || (typeof evidence['capturedTo'] === 'string' && Date.parse(found.timestamp) > Date.parse(evidence['capturedTo']))))
1397
+ sourceDiagnostics.push('captured-source-window-mismatch');
1398
+ if (found.payloadDigest !== receipt['payloadDigest'])
1399
+ sourceDiagnostics.push('source-payload-conflict');
1400
+ if (!found.responseId) {
1401
+ independent = false;
1402
+ diagnostics.push('source-receipt-identity-unavailable');
1403
+ }
1404
+ witnesses.push({ evidenceKey: key, tokensTotal: found.totals.total, reportedTotalBasis: 'raw-inclusive' });
1405
+ if (found.totals.total === null)
1406
+ allKnown = false;
1407
+ else
1408
+ scopedTotal += found.totals.total;
1409
+ for (const [field, value] of Object.entries({ tokensIn: found.totals.input, tokensOut: found.totals.output, tokensCacheRead: found.totals.cachedInput,
1410
+ tokensCacheWrite: found.totals.cachedWrite ?? null, tokensReasoning: found.totals.reasoning }))
1411
+ scopedDimensions[field] = scopedDimensions[field] === null || value === null ? null : scopedDimensions[field] + value;
1412
+ }
1413
+ if (allKnown && !Number.isSafeInteger(scopedTotal))
1414
+ sourceDiagnostics.push('source-scoped-aggregate-overflow:total');
1415
+ for (const [field, value] of Object.entries(scopedDimensions))
1416
+ if (value !== null && !Number.isSafeInteger(value))
1417
+ sourceDiagnostics.push('source-scoped-aggregate-overflow:' + field);
1418
+ if (parsed && !('error' in parsed))
1419
+ for (const scope of parsed.scopeDiagnostics ?? []) {
1420
+ // A matching prefix and recorded end bound establish the SAME used witness scope.
1421
+ if (scope.receiptCount !== matchedSourceKeys.size || maxSourceRecord < 0 || maxSourceRecord > scope.sourceRecord
1422
+ || (scope.timestamp !== null && typeof evidence['capturedTo'] === 'string' && Date.parse(scope.timestamp) > Date.parse(evidence['capturedTo'])))
1423
+ continue;
1424
+ for (const diagnostic of scope.diagnostics)
1425
+ sourceDiagnostics.push('source-scope:' + diagnostic);
1426
+ if (scope.witness)
1427
+ for (const [field, actual] of Object.entries({ total: allKnown ? scopedTotal : null, input: scopedDimensions['tokensIn'], output: scopedDimensions['tokensOut'], cachedInput: scopedDimensions['tokensCacheRead'], cachedWrite: scopedDimensions['tokensCacheWrite'], reasoning: scopedDimensions['tokensReasoning'] })) {
1428
+ const witness = scope.witness[field];
1429
+ if (actual != null && witness != null && actual !== witness)
1430
+ sourceDiagnostics.push('source-scoped-witness-mismatch:' + field);
1431
+ }
1432
+ }
1433
+ if (scopeAssociations.size > 1)
1434
+ sourceDiagnostics.push('captured-mixed-turn-association');
1435
+ // Verify the captured receipt subset, not an expanded session's later cumulative total.
1436
+ // A malformed record still leaves the source census undecidable; receipt mutations are
1437
+ // detected above by their captured payload hashes and identities.
1438
+ if (parsed && !('error' in parsed) && parsed.diagnostics?.includes('malformed-record'))
1439
+ sourceDiagnostics.push('source:malformed-record');
1440
+ if (Array.isArray(row['usageDiagnostics']) && row['usageDiagnostics'].some((d) => typeof d === 'string' && /invalid|mismatch|conflict|reset|overflow|foreign/.test(d)))
1441
+ sourceDiagnostics.push('captured-source-was-invalid');
1442
+ const claimTotal = row['tokensTotal'] ?? row['tokens'];
1443
+ if (parsed && !('error' in parsed) && allKnown && Number.isSafeInteger(scopedTotal) && claimTotal !== scopedTotal)
1444
+ sourceDiagnostics.push('source-claim-total-mismatch');
1445
+ if (parsed && !('error' in parsed) && allKnown && row['tokens'] != null && row['tokens'] !== scopedTotal)
1446
+ sourceDiagnostics.push('source-compatibility-total-mismatch');
1447
+ for (const [field, value] of Object.entries(scopedDimensions))
1448
+ if (parsed && !('error' in parsed) && row[field] != null && value !== null && row[field] !== value)
1449
+ sourceDiagnostics.push('source-dimension-mismatch:' + field);
1450
+ if (!captured.length) {
1451
+ rows.push(row);
1452
+ independent = false;
1453
+ diagnostics.push('source-scope-empty');
1454
+ }
1455
+ }
1456
+ if (!rows.length && selected.some((row) => row['summary'] === true))
1457
+ diagnostics.push('projection-only-spend-unavailable');
1458
+ if (sourceDiagnostics.length)
1459
+ for (const row of rows)
1460
+ row['usageDiagnostics'] = [...(Array.isArray(row['usageDiagnostics']) ? row['usageDiagnostics'] : []), 'source-payload-conflict'];
1461
+ return build({ sourceKind: 'fa-ledger', sourcePath: faPath, runId, rows,
1462
+ ...(independent ? { expected, witnesses } : {}), moneyObservations, diagnostics, sourceDiagnostics });
1463
+ }
1464
+ if (chosen === 'claude-transcript') {
1465
+ if (claudeAmbiguous)
1466
+ return insufficient('source-selection-ambiguous');
1467
+ const legacy = deriveCostLedger({ ...(opts.epsilon !== undefined ? { epsilon: opts.epsilon } : {}), projectRoot: project, ...(claudeRefs[0] !== undefined ? { runId: claudeRefs[0].runId } : opts.runId !== undefined ? { runId: opts.runId } : {}), ...(opts.slug !== undefined ? { slug: opts.slug } : {}), ...(opts.projectsRoot !== undefined ? { projectsRoot: opts.projectsRoot } : {}) });
1468
+ if (!legacy || (opts.slug !== undefined && legacy.slug !== opts.slug))
1469
+ return insufficient('claude-source-missing');
1470
+ if (legacy.authoritativeEvidence) {
1471
+ roots.push(...legacy.authoritativeEvidence.roots);
1472
+ files.push(...legacy.authoritativeEvidence.files);
1473
+ }
1474
+ const report = build({ sourceKind: 'claude-transcript', sourcePath: opts.projectsRoot ?? claudeProjectsRoot(), runId: legacy.runId,
1475
+ rows: legacy.rows.map((row) => ({ runId: row.runId, stage: row.stage, model: row.model, family: 'claude', attempt: row.attempt,
1476
+ tokensTotal: row.weightedTokens, reportedTotalBasis: 'weighted-input-equivalent', evidenceKey: JSON.stringify([row.runId, row.stage, row.attempt]) })), diagnostics: ['legacy-weighted-transcript-view'] });
1477
+ return { ...report, legacyCostLedger: legacy };
1478
+ }
1479
+ return insufficient('no-selected-project-source');
1480
+ }
1481
+ export function renderStageUsageReport(report) {
1482
+ const number = (n) => n === null ? 'unavailable' : String(n);
1483
+ const rows = report.rows.map((row) => `${row.stage ?? 'unattributed'} | ${row.model ?? 'unknown model'} | total ${number(row.tokensTotal)} (${row.reportedTotalBasis}) | input ${number(row.tokensIn)} cache-read ${number(row.tokensCacheRead)} cache-write ${number(row.tokensCacheWrite)} output ${number(row.tokensOut)} | estimated USD ${number(row.estimatedCostUsd)}`);
1484
+ return [`usage --by-stage: ${report.verdict} — ${report.sourceKind} ${report.sourcePath}`, `metric: ${report.metric}; known subtotal ${number(report.knownRunTotalTokens)}; full total ${number(report.runTotalTokens)}`,
1485
+ `conservation: ${report.conservation.status}; inventory: ${report.inventory.status}; source verification: ${report.sourceVerification.status}; source verified total ${number(report.sourceVerifiedTotalTokens)}`,
1486
+ `reported USD ${number(report.reportedCostUsd)}; known reported subtotal ${number(report.knownReportedCostUsd)}; money coverage ${report.reportedCostCoverage.status}`,
1487
+ `estimated USD ${number(report.estimatedCostUsd)}; known estimated subtotal ${number(report.knownEstimatedCostUsd)}; billed USD unavailable (not observed)`, ...rows,
1488
+ ...report.diagnostics.map((d) => 'diagnostic: ' + d), ...report.sourceVerification.diagnostics.map((d) => 'source: ' + d)].join('\n');
1489
+ }
1027
1490
  //# sourceMappingURL=cost-ledger.js.map