@dzhechkov/harness-core 0.8.45 → 0.8.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dz-manifest.json +206 -174
- package/README.md +136 -8
- package/dist/agentdb-index.d.ts.map +1 -1
- package/dist/agentdb-index.js +32 -2
- package/dist/agentdb-index.js.map +1 -1
- package/dist/agentdb-snapshot.d.ts +14 -1
- package/dist/agentdb-snapshot.d.ts.map +1 -1
- package/dist/agentdb-snapshot.js +100 -4
- package/dist/agentdb-snapshot.js.map +1 -1
- package/dist/apply-leg.d.ts +9 -1
- package/dist/apply-leg.d.ts.map +1 -1
- package/dist/apply-leg.js +47 -10
- package/dist/apply-leg.js.map +1 -1
- package/dist/architecture.d.ts +0 -5
- package/dist/architecture.d.ts.map +1 -1
- package/dist/architecture.js +25 -2
- package/dist/architecture.js.map +1 -1
- package/dist/codex-rollouts.d.ts +35 -7
- package/dist/codex-rollouts.d.ts.map +1 -1
- package/dist/codex-rollouts.js +201 -113
- package/dist/codex-rollouts.js.map +1 -1
- package/dist/cost-ledger.d.ts +42 -3
- package/dist/cost-ledger.d.ts.map +1 -1
- package/dist/cost-ledger.js +478 -15
- package/dist/cost-ledger.js.map +1 -1
- package/dist/feature-adr-routing.d.ts +16 -0
- package/dist/feature-adr-routing.d.ts.map +1 -1
- package/dist/feature-adr-routing.js +21 -0
- package/dist/feature-adr-routing.js.map +1 -1
- package/dist/guard.d.ts +7 -0
- package/dist/guard.d.ts.map +1 -1
- package/dist/guard.js +48 -0
- package/dist/guard.js.map +1 -1
- package/dist/index.d.ts +13 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -1
- package/dist/loop-plan.d.ts +4 -0
- package/dist/loop-plan.d.ts.map +1 -1
- package/dist/loop-plan.js +4 -0
- package/dist/loop-plan.js.map +1 -1
- package/dist/mutation-gate.d.ts +36 -1
- package/dist/mutation-gate.d.ts.map +1 -1
- package/dist/mutation-gate.js +109 -19
- package/dist/mutation-gate.js.map +1 -1
- package/dist/npm-homepage.d.ts +64 -0
- package/dist/npm-homepage.d.ts.map +1 -0
- package/dist/npm-homepage.js +109 -0
- package/dist/npm-homepage.js.map +1 -0
- package/dist/operations.d.ts +6 -0
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +150 -24
- package/dist/operations.js.map +1 -1
- package/dist/pack-inventory.d.ts +13 -0
- package/dist/pack-inventory.d.ts.map +1 -1
- package/dist/pack-inventory.js +28 -2
- package/dist/pack-inventory.js.map +1 -1
- package/dist/parity.d.ts.map +1 -1
- package/dist/parity.js +4 -1
- package/dist/parity.js.map +1 -1
- package/dist/publish-sibling-drift.d.ts +43 -1
- package/dist/publish-sibling-drift.d.ts.map +1 -1
- package/dist/publish-sibling-drift.js +269 -13
- package/dist/publish-sibling-drift.js.map +1 -1
- package/dist/publish.d.ts +5 -1
- package/dist/publish.d.ts.map +1 -1
- package/dist/publish.js +18 -1
- package/dist/publish.js.map +1 -1
- package/dist/qe-bridge.d.ts +63 -1
- package/dist/qe-bridge.d.ts.map +1 -1
- package/dist/qe-bridge.js +82 -0
- package/dist/qe-bridge.js.map +1 -1
- package/dist/release-package-audit.d.ts +86 -0
- package/dist/release-package-audit.d.ts.map +1 -0
- package/dist/release-package-audit.js +272 -0
- package/dist/release-package-audit.js.map +1 -0
- package/dist/release.d.ts +8 -1
- package/dist/release.d.ts.map +1 -1
- package/dist/release.js +58 -32
- package/dist/release.js.map +1 -1
- package/dist/round-exec-claim.d.ts +36 -0
- package/dist/round-exec-claim.d.ts.map +1 -0
- package/dist/round-exec-claim.js +17 -0
- package/dist/round-exec-claim.js.map +1 -0
- package/dist/round.d.ts +10 -1
- package/dist/round.d.ts.map +1 -1
- package/dist/round.js +5 -1
- package/dist/round.js.map +1 -1
- package/dist/run-records.d.ts +11 -0
- package/dist/run-records.d.ts.map +1 -1
- package/dist/run-records.js +124 -20
- package/dist/run-records.js.map +1 -1
- package/dist/session-retro.d.ts +33 -0
- package/dist/session-retro.d.ts.map +1 -1
- package/dist/session-retro.js +170 -34
- package/dist/session-retro.js.map +1 -1
- package/dist/sign.d.ts +44 -0
- package/dist/sign.d.ts.map +1 -1
- package/dist/sign.js +126 -1
- package/dist/sign.js.map +1 -1
- package/dist/stage-usage.d.ts +148 -0
- package/dist/stage-usage.d.ts.map +1 -0
- package/dist/stage-usage.js +261 -0
- package/dist/stage-usage.js.map +1 -0
- package/dist/statusline.d.ts +23 -0
- package/dist/statusline.d.ts.map +1 -1
- package/dist/statusline.js +167 -1
- package/dist/statusline.js.map +1 -1
- package/dist/workflow-run-dispatch.d.ts +21 -19
- package/dist/workflow-run-dispatch.d.ts.map +1 -1
- package/dist/workflow-run-dispatch.js +206 -95
- package/dist/workflow-run-dispatch.js.map +1 -1
- package/dist/workflow-run.d.ts +12 -3
- package/dist/workflow-run.d.ts.map +1 -1
- package/dist/workflow-run.js +29 -2
- package/dist/workflow-run.js.map +1 -1
- package/package.json +4 -4
- package/sbom.json +309 -229
- package/src/agentdb-index.ts +31 -2
- package/src/agentdb-snapshot.ts +105 -4
- package/src/apply-leg.ts +47 -10
- package/src/architecture.ts +15 -2
- package/src/codex-rollouts.ts +155 -147
- package/src/cost-ledger.ts +308 -19
- package/src/feature-adr-routing.ts +22 -0
- package/src/guard.ts +52 -0
- package/src/index.ts +19 -3
- package/src/loop-plan.ts +8 -0
- package/src/mutation-gate.ts +153 -19
- package/src/npm-homepage.ts +142 -0
- package/src/operations.ts +177 -22
- package/src/pack-inventory.ts +27 -2
- package/src/parity.ts +4 -1
- package/src/publish-sibling-drift.ts +214 -11
- package/src/publish.ts +20 -1
- package/src/qe-bridge.ts +86 -1
- package/src/release-package-audit.ts +264 -0
- package/src/release.ts +55 -22
- package/src/round-exec-claim.ts +43 -0
- package/src/round.ts +8 -0
- package/src/run-records.ts +101 -22
- package/src/session-retro.ts +153 -31
- package/src/sign.ts +136 -1
- package/src/stage-usage.ts +191 -0
- package/src/statusline.ts +149 -1
- package/src/workflow-run-dispatch.ts +148 -89
- package/src/workflow-run.ts +40 -4
- package/dist/ledger-cost-fill.d.ts +0 -58
- package/dist/ledger-cost-fill.d.ts.map +0 -1
- package/dist/ledger-cost-fill.js +0 -78
- package/dist/ledger-cost-fill.js.map +0 -1
- package/dist/retro.d.ts +0 -131
- package/dist/retro.d.ts.map +0 -1
- package/dist/retro.js +0 -207
- package/dist/retro.js.map +0 -1
- package/dist/sbom.d.ts +0 -42
- package/dist/sbom.d.ts.map +0 -1
- package/dist/sbom.js +0 -120
- package/dist/sbom.js.map +0 -1
package/dist/cost-ledger.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
547
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|