@trazum/core 1.37.0 → 1.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -92,6 +92,33 @@ running on different models in different sources (judged on each source's
92
92
  dearest model, so a stray experiment is not a migration), and names sources
93
93
  where caching loses money while the aggregate pays off.
94
94
 
95
+ ## The plan
96
+
97
+ `buildPlan(report, levers, pricingLastReviewed)` turns a report and its
98
+ levers into a ranked `PlanDocument`: route and batch on one slice arrive as a
99
+ single pre-combined action (`combinedUsd`, never a sum), the truncation
100
+ action's stake is the measured retry bill, and a cache action exists only for
101
+ a settled loss — an unsettled verdict is a missing field, and "add the field"
102
+ is the report's advice, not a plan's. Projected savings and measured stakes
103
+ are totalled separately; every action carries typed assumptions
104
+ (`PlanAssumption`) the log cannot confirm, as data rather than prose, so the
105
+ renderer localizes them and a later verification can match them
106
+ structurally. `planLabelName` renders the unlabelled bucket without leaking
107
+ the sentinel.
108
+
109
+ ## Did it work?
110
+
111
+ `verifyPlan(plan, newerReport, { currentPricingLastReviewed })` holds a saved
112
+ plan to the report of a later log, with three outcomes and never two:
113
+ `arrived`, `not-arrived`, or `cannot-tell` — the workload vanished, the
114
+ fields the detection needs stopped being recorded, or tokens cannot say
115
+ which tier billed them. Differences carry the world's measured movement from
116
+ the plan's recorded baseline (calls, tokens per call, both numbers and never
117
+ a verdict), a repricing between the two documents is flagged rather than
118
+ silently priced through, and each verdict says whether it fails a gate:
119
+ `not-arrived` always, `fields-stopped` too — a team must not pass on the
120
+ strength of its own log's silence — and a vanished workload never.
121
+
95
122
  ## Comparing two versions
96
123
 
97
124
  ```ts
package/dist/index.d.ts CHANGED
@@ -13,6 +13,10 @@ export { coverageDrift, COVERAGE_FIELDS, COVERAGE_DRIFT_MIN } from './coverage-d
13
13
  export { explainGateFailure, gateMargin, GATE_MARGIN_TIGHT } from './gate-explain.js';
14
14
  export { measuredUsage, labelCoverage, MIN_SCALE_DAYS, SCALE_TO_DAYS } from './measured-profile.js';
15
15
  export { assignSources, fleetRollup } from './fleet.js';
16
+ export { buildPlan, planLabelName } from './plan.js';
17
+ export type { PlanAction, PlanActionKind, PlanAssumption, PlanDocument } from './plan.js';
18
+ export { verifyPlan } from './verify.js';
19
+ export type { CannotTellReason, PlanVerification, VerifiedAction, VerifyOutcome } from './verify.js';
16
20
  export type { FleetSource, FleetRollup } from './fleet.js';
17
21
  export type { MeasuredUsage, LabelCoverage } from './measured-profile.js';
18
22
  export type { GateExplanation } from './gate-explain.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -18,6 +18,8 @@ export { coverageDrift, COVERAGE_FIELDS, COVERAGE_DRIFT_MIN } from './coverage-d
18
18
  export { explainGateFailure, gateMargin, GATE_MARGIN_TIGHT } from './gate-explain.js';
19
19
  export { measuredUsage, labelCoverage, MIN_SCALE_DAYS, SCALE_TO_DAYS } from './measured-profile.js';
20
20
  export { assignSources, fleetRollup } from './fleet.js';
21
+ export { buildPlan, planLabelName } from './plan.js';
22
+ export { verifyPlan } from './verify.js';
21
23
  // The same tokens at another model's rates — arithmetic, not advice, and it
22
24
  // refuses to price a call the target could not have accepted. See reprice.ts.
23
25
  // The shape of a call's input — the half of the bill a total could only name.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/plan.d.ts ADDED
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Not a list of findings — a ranked, costed, non-additive plan of what to do.
3
+ *
4
+ * The report names findings; a person then decides what to do first by doing
5
+ * arithmetic in their head, and head-arithmetic on savings gets done by
6
+ * *adding* them — which the levers module has documented as wrong since it
7
+ * shipped ($12.60 plus $10.50 against a $21.00 slice). This module does the
8
+ * composition once, correctly, and attaches to every action the things the
9
+ * log cannot confirm, because a plan that hides its assumptions is advice
10
+ * pretending to be arithmetic.
11
+ *
12
+ * **Everything here is derived from figures the report already computed.**
13
+ * Route and batch come from `billLevers` (combined, never summed). The
14
+ * truncation action's stake is the retry bill `truncationRetries` measured.
15
+ * The cache action's stake is `cacheEconomics`' own delta. Nothing is
16
+ * invented, and each action carries how to check the part that is not
17
+ * arithmetic.
18
+ *
19
+ * **The total is stated honestly.** Actions on *different* slices add
20
+ * cleanly; the one composition that does not add — route and batch on the
21
+ * same slice — arrives already combined inside a single action, so the
22
+ * plan's total is a sum of non-overlapping figures by construction. Measured
23
+ * stakes (money already spent on retries, money already lost to caching) are
24
+ * totalled separately from projected savings: "what you would save" and
25
+ * "what you already paid" are different columns, and merging them makes a
26
+ * number that is neither.
27
+ */
28
+ import type { UsageProfileReport } from './usage.js';
29
+ import type { BillLevers } from './levers.js';
30
+ export type PlanActionKind = 'route' | 'batch' | 'route+batch' | 'fix-truncation' | 'fix-caching';
31
+ /**
32
+ * What the log cannot confirm, as data rather than prose.
33
+ *
34
+ * Rendering lives with whoever renders — the CLI localizes these, and 1.39's
35
+ * verification can match them structurally. An English sentence baked in here
36
+ * would be a browser-safe module deciding the reader's language.
37
+ */
38
+ export type PlanAssumption =
39
+ /** The cheaper model can actually do this work — quality, not arithmetic. */
40
+ {
41
+ kind: 'model-capability';
42
+ model: string;
43
+ }
44
+ /** These calls tolerate a batch window's latency. */
45
+ | {
46
+ kind: 'batch-window';
47
+ }
48
+ /** The truncation-retry pairing is real — the log sees shapes, not content. */
49
+ | {
50
+ kind: 'retry-pattern-real';
51
+ }
52
+ /** A max_tokens the answers fit inside removes the retry pair. */
53
+ | {
54
+ kind: 'max-tokens-fits';
55
+ }
56
+ /** The traffic pattern holds — a cache underwater on this log may pay on other traffic. */
57
+ | {
58
+ kind: 'traffic-pattern-holds';
59
+ };
60
+ export interface PlanAction {
61
+ kind: PlanActionKind;
62
+ /** The workload this acts on. `UNLABELLED` renders as the unlabelled bucket. */
63
+ label: string;
64
+ model: string;
65
+ /**
66
+ * Projected saving per the log's own period, for route/batch — or null for
67
+ * the measured-stake actions, whose money is in `stakeUsd` instead. Never
68
+ * both: a projection and a measurement in one field is a number that is
69
+ * neither.
70
+ */
71
+ savingUsd: number | null;
72
+ /**
73
+ * Money already measured against this problem — the retry bill, the cache
74
+ * loss. Null for the projected actions.
75
+ */
76
+ stakeUsd: number | null;
77
+ /** What the log cannot confirm. Every entry is a human's question to answer. */
78
+ assumes: PlanAssumption[];
79
+ /** How to check the assumption, when a Trazum command can. */
80
+ check: string | null;
81
+ /** What this action does, in one machine-stable keyword per detail. */
82
+ detail: {
83
+ /** Route target, when the action moves the calls. */
84
+ routeTo?: {
85
+ id: string;
86
+ displayName: string;
87
+ };
88
+ /** The measured pieces behind a stake. */
89
+ measured?: Record<string, number>;
90
+ /**
91
+ * The slice as it was when the plan was made — what a later verification
92
+ * compares the newer log against. Without this the plan is a prediction
93
+ * with no record of the world it was made in, and "calls doubled" could
94
+ * never be told from "the prediction was wrong".
95
+ */
96
+ baseline?: {
97
+ calls: number;
98
+ usd: number;
99
+ inputPerCallTokens: number;
100
+ outputPerCallTokens: number;
101
+ };
102
+ };
103
+ }
104
+ export interface PlanDocument {
105
+ /** Same contract discipline as the profile JSON. */
106
+ schemaVersion: 1;
107
+ /** The period the plan's figures cover, or null when the log had no clock. */
108
+ span: {
109
+ fromMs: number;
110
+ toMs: number;
111
+ } | null;
112
+ /** The price table behind every dollar here. */
113
+ pricingLastReviewed: string;
114
+ /** Ranked: largest money first, projected or staked alike. */
115
+ actions: PlanAction[];
116
+ /**
117
+ * Projected savings summed — additive by construction, because same-slice
118
+ * compositions arrive pre-combined in one action.
119
+ */
120
+ projectedSavingUsd: number;
121
+ /** Measured stakes summed: money already paid to problems this plan names. */
122
+ measuredStakeUsd: number;
123
+ /** The bill the plan was made against. */
124
+ totalUsd: number;
125
+ }
126
+ /**
127
+ * Builds the plan from a report and its levers.
128
+ *
129
+ * `pricingLastReviewed` is passed in rather than imported so the plan records
130
+ * the catalogue that actually priced it — an overlay's date when one was in
131
+ * effect, which 1.39's verification needs to tell "the prediction was wrong"
132
+ * from "the prices changed".
133
+ */
134
+ export declare function buildPlan(report: UsageProfileReport, levers: BillLevers, pricingLastReviewed: string): PlanDocument;
135
+ /** Renders `UNLABELLED` for humans without leaking the sentinel. */
136
+ export declare function planLabelName(label: string, unlabelled: string): string;
137
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,OAAO,GAAG,aAAa,GAAG,gBAAgB,GAAG,aAAa,CAAC;AAElG;;;;;;GAMG;AACH,MAAM,MAAM,cAAc;AACxB,6EAA6E;AAC3E;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAC7C,qDAAqD;GACnD;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE;AAC1B,+EAA+E;GAC7E;IAAE,IAAI,EAAE,oBAAoB,CAAA;CAAE;AAChC,kEAAkE;GAChE;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE;AAC7B,2FAA2F;GACzF;IAAE,IAAI,EAAE,uBAAuB,CAAA;CAAE,CAAC;AAEtC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,cAAc,CAAC;IACrB,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,gFAAgF;IAChF,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,8DAA8D;IAC9D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,uEAAuE;IACvE,MAAM,EAAE;QACN,qDAAqD;QACrD,OAAO,CAAC,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC;QAC9C,0CAA0C;QAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAClC;;;;;WAKG;QACH,QAAQ,CAAC,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAC;YAAC,kBAAkB,EAAE,MAAM,CAAC;YAAC,mBAAmB,EAAE,MAAM,CAAA;SAAE,CAAC;KACpG,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,oDAAoD;IACpD,aAAa,EAAE,CAAC,CAAC;IACjB,8EAA8E;IAC9E,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9C,gDAAgD;IAChD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8DAA8D;IAC9D,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB;;;OAGG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAC;IACzB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,UAAU,EAClB,mBAAmB,EAAE,MAAM,GAC1B,YAAY,CAwGd;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEvE"}
package/dist/plan.js ADDED
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Not a list of findings — a ranked, costed, non-additive plan of what to do.
3
+ *
4
+ * The report names findings; a person then decides what to do first by doing
5
+ * arithmetic in their head, and head-arithmetic on savings gets done by
6
+ * *adding* them — which the levers module has documented as wrong since it
7
+ * shipped ($12.60 plus $10.50 against a $21.00 slice). This module does the
8
+ * composition once, correctly, and attaches to every action the things the
9
+ * log cannot confirm, because a plan that hides its assumptions is advice
10
+ * pretending to be arithmetic.
11
+ *
12
+ * **Everything here is derived from figures the report already computed.**
13
+ * Route and batch come from `billLevers` (combined, never summed). The
14
+ * truncation action's stake is the retry bill `truncationRetries` measured.
15
+ * The cache action's stake is `cacheEconomics`' own delta. Nothing is
16
+ * invented, and each action carries how to check the part that is not
17
+ * arithmetic.
18
+ *
19
+ * **The total is stated honestly.** Actions on *different* slices add
20
+ * cleanly; the one composition that does not add — route and batch on the
21
+ * same slice — arrives already combined inside a single action, so the
22
+ * plan's total is a sum of non-overlapping figures by construction. Measured
23
+ * stakes (money already spent on retries, money already lost to caching) are
24
+ * totalled separately from projected savings: "what you would save" and
25
+ * "what you already paid" are different columns, and merging them makes a
26
+ * number that is neither.
27
+ */
28
+ import { UNLABELLED, cacheEconomics } from './usage.js';
29
+ /**
30
+ * Builds the plan from a report and its levers.
31
+ *
32
+ * `pricingLastReviewed` is passed in rather than imported so the plan records
33
+ * the catalogue that actually priced it — an overlay's date when one was in
34
+ * effect, which 1.39's verification needs to tell "the prediction was wrong"
35
+ * from "the prices changed".
36
+ */
37
+ export function buildPlan(report, levers, pricingLastReviewed) {
38
+ const actions = [];
39
+ /** The slice as the plan saw it, recorded so verification has a "before". */
40
+ const baselineOf = (label, model) => {
41
+ const slice = report.byLabelAndModel.find((s) => s.label === label && s.model === model);
42
+ if (slice === undefined || slice.breakdown.calls === 0)
43
+ return undefined;
44
+ const b = slice.breakdown;
45
+ return {
46
+ calls: b.calls,
47
+ usd: b.totalUsd,
48
+ inputPerCallTokens: (b.inputTokens + b.cacheReadTokens + b.cacheWriteTokens) / b.calls,
49
+ outputPerCallTokens: b.outputTokens / b.calls,
50
+ };
51
+ };
52
+ for (const slice of levers.slices) {
53
+ const assumes = [];
54
+ let kind;
55
+ if (slice.route !== null && slice.batch !== null) {
56
+ kind = 'route+batch';
57
+ assumes.push({ kind: 'model-capability', model: slice.route.candidate.displayName });
58
+ assumes.push({ kind: 'batch-window' });
59
+ }
60
+ else if (slice.route !== null) {
61
+ kind = 'route';
62
+ assumes.push({ kind: 'model-capability', model: slice.route.candidate.displayName });
63
+ }
64
+ else if (slice.batch !== null) {
65
+ kind = 'batch';
66
+ assumes.push({ kind: 'batch-window' });
67
+ }
68
+ else {
69
+ continue;
70
+ }
71
+ actions.push({
72
+ kind,
73
+ label: slice.label,
74
+ model: slice.model,
75
+ savingUsd: slice.combinedUsd,
76
+ stakeUsd: null,
77
+ assumes,
78
+ check: slice.route !== null ? 'trazum route <log> --prompt-file <prompt> --cases <cases>' : null,
79
+ detail: {
80
+ ...(slice.route !== null ? { routeTo: slice.route.candidate } : {}),
81
+ baseline: baselineOf(slice.label, slice.model),
82
+ },
83
+ });
84
+ }
85
+ for (const row of report.truncationRetries) {
86
+ actions.push({
87
+ kind: 'fix-truncation',
88
+ label: row.label,
89
+ model: row.model,
90
+ savingUsd: null,
91
+ stakeUsd: row.wastedUsd + row.retryUsd,
92
+ assumes: [{ kind: 'retry-pattern-real' }, { kind: 'max-tokens-fits' }],
93
+ check: null,
94
+ detail: {
95
+ measured: {
96
+ wastedUsd: row.wastedUsd,
97
+ retryUsd: row.retryUsd,
98
+ retried: row.retried,
99
+ truncatedCalls: row.truncatedCalls,
100
+ },
101
+ baseline: baselineOf(row.label, row.model),
102
+ },
103
+ });
104
+ }
105
+ for (const slice of report.byLabelAndModel) {
106
+ const economics = cacheEconomics(slice.breakdown);
107
+ // Only a settled loss becomes an action: an unsettled verdict is a
108
+ // missing field, and "add the field" is the report's advice, not a plan's.
109
+ if (economics.verdict !== 'lost-money' || economics.worstCaseVerdict !== economics.verdict)
110
+ continue;
111
+ actions.push({
112
+ kind: 'fix-caching',
113
+ label: slice.label,
114
+ model: slice.model,
115
+ savingUsd: null,
116
+ stakeUsd: economics.deltaUsd,
117
+ assumes: [{ kind: 'traffic-pattern-holds' }],
118
+ check: null,
119
+ detail: {
120
+ measured: {
121
+ spentUsd: economics.spentUsd,
122
+ withoutCachingUsd: economics.withoutCachingUsd,
123
+ },
124
+ baseline: baselineOf(slice.label, slice.model),
125
+ },
126
+ });
127
+ }
128
+ actions.sort((a, b) => (b.savingUsd ?? b.stakeUsd ?? 0) - (a.savingUsd ?? a.stakeUsd ?? 0));
129
+ return {
130
+ schemaVersion: 1,
131
+ span: report.span === null ? null : { fromMs: report.span.fromMs, toMs: report.span.toMs },
132
+ pricingLastReviewed,
133
+ actions,
134
+ projectedSavingUsd: actions.reduce((sum, a) => sum + (a.savingUsd ?? 0), 0),
135
+ measuredStakeUsd: actions.reduce((sum, a) => sum + (a.stakeUsd ?? 0), 0),
136
+ totalUsd: report.total.totalUsd,
137
+ };
138
+ }
139
+ /** Renders `UNLABELLED` for humans without leaking the sentinel. */
140
+ export function planLabelName(label, unlabelled) {
141
+ return label === UNLABELLED ? unlabelled : label;
142
+ }
143
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Did it work? — the plan held to the log that came after it.
3
+ *
4
+ * Every optimisation tool says what you *would* save; almost none says what
5
+ * you *did*. This module takes a saved plan and a newer report and answers
6
+ * per action, with **three outcomes and never two**: the change arrived, the
7
+ * change did not arrive, or *it cannot be told* — because the workload
8
+ * vanished, the fields the detection needs stopped being recorded, or the
9
+ * log simply cannot see the thing (tokens do not say which tier billed
10
+ * them). The third outcome is the honest one, and the one every other tool
11
+ * renders as the first.
12
+ *
13
+ * **Differences are attributed, not just stated.** A predicted saving that
14
+ * did not appear in the bill is decomposed against the plan's recorded
15
+ * baseline: the calls moved, the output per call moved, the input per call
16
+ * moved — measured ratios with names, never a single number that blames
17
+ * nobody. And a plan priced under one catalogue verified under another says
18
+ * so: the tool must not blame a team for a saving that arithmetic revoked.
19
+ *
20
+ * Browser-safe like everything here: two documents in, one verdict out.
21
+ */
22
+ import type { UsageProfileReport } from './usage.js';
23
+ import type { PlanAction, PlanDocument } from './plan.js';
24
+ export type VerifyOutcome = 'arrived' | 'not-arrived' | 'cannot-tell';
25
+ /**
26
+ * Why an action cannot be told. The distinction matters to the gate: a
27
+ * workload that vanished is the world's doing; a log that stopped recording
28
+ * the fields the detection needs is the team's, and "not recorded" must not
29
+ * read as "fixed".
30
+ */
31
+ export type CannotTellReason = 'workload-vanished' | 'fields-stopped' | 'tier-not-recorded';
32
+ export interface VerifiedAction {
33
+ /** The plan's action, verbatim — the prediction being judged. */
34
+ action: PlanAction;
35
+ outcome: VerifyOutcome;
36
+ reason: CannotTellReason | null;
37
+ /**
38
+ * What the newer log measured for this slice, per kind: where the money
39
+ * sits now, the new retry bill, the new cache delta. Keys are stable.
40
+ */
41
+ observed: Record<string, number | string | null>;
42
+ /**
43
+ * The world's movement between the two logs, from the plan's recorded
44
+ * baseline: never a verdict, always the measured before and after.
45
+ */
46
+ attribution: {
47
+ calls?: {
48
+ before: number;
49
+ after: number;
50
+ };
51
+ inputPerCallTokens?: {
52
+ before: number;
53
+ after: number;
54
+ };
55
+ outputPerCallTokens?: {
56
+ before: number;
57
+ after: number;
58
+ };
59
+ } | null;
60
+ /**
61
+ * Whether this action fails `--gate`. `not-arrived` always does;
62
+ * `cannot-tell` does only for `fields-stopped` — a team that degraded its
63
+ * own log must not pass the gate on the strength of the silence. A
64
+ * vanished workload and an unrecordable tier fail nothing.
65
+ */
66
+ gateFailing: boolean;
67
+ }
68
+ export interface PlanVerification {
69
+ schemaVersion: 1;
70
+ /** When the plan was made, when it carries the stamp. */
71
+ planCreatedAt: string | null;
72
+ /** The catalogue that priced the plan, and the one pricing this check. */
73
+ planPricing: string;
74
+ currentPricing: string;
75
+ /**
76
+ * True when those differ: every dollar comparison here is then two
77
+ * measurements under two price lists, and the rendering must say so
78
+ * rather than let a repricing read as a team's failure.
79
+ */
80
+ pricesChanged: boolean;
81
+ actions: VerifiedAction[];
82
+ arrived: number;
83
+ notArrived: number;
84
+ cannotTell: number;
85
+ gateFailures: number;
86
+ }
87
+ export declare function verifyPlan(plan: PlanDocument & {
88
+ createdAt?: string;
89
+ }, report: UsageProfileReport, options: {
90
+ currentPricingLastReviewed: string;
91
+ }): PlanVerification;
92
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE1D,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,aAAa,GAAG,aAAa,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;AAE5F,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAChC;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC;IACjD;;;OAGG;IACH,WAAW,EAAE;QACX,KAAK,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAC1C,kBAAkB,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QACvD,mBAAmB,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;KACzD,GAAG,IAAI,CAAC;IACT;;;;;OAKG;IACH,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,CAAC,CAAC;IACjB,yDAAyD;IACzD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,0EAA0E;IAC1E,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AA+BD,wBAAgB,UAAU,CACxB,IAAI,EAAE,YAAY,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EAC3C,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE;IAAE,0BAA0B,EAAE,MAAM,CAAA;CAAE,GAC9C,gBAAgB,CA4JlB"}
package/dist/verify.js ADDED
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Did it work? — the plan held to the log that came after it.
3
+ *
4
+ * Every optimisation tool says what you *would* save; almost none says what
5
+ * you *did*. This module takes a saved plan and a newer report and answers
6
+ * per action, with **three outcomes and never two**: the change arrived, the
7
+ * change did not arrive, or *it cannot be told* — because the workload
8
+ * vanished, the fields the detection needs stopped being recorded, or the
9
+ * log simply cannot see the thing (tokens do not say which tier billed
10
+ * them). The third outcome is the honest one, and the one every other tool
11
+ * renders as the first.
12
+ *
13
+ * **Differences are attributed, not just stated.** A predicted saving that
14
+ * did not appear in the bill is decomposed against the plan's recorded
15
+ * baseline: the calls moved, the output per call moved, the input per call
16
+ * moved — measured ratios with names, never a single number that blames
17
+ * nobody. And a plan priced under one catalogue verified under another says
18
+ * so: the tool must not blame a team for a saving that arithmetic revoked.
19
+ *
20
+ * Browser-safe like everything here: two documents in, one verdict out.
21
+ */
22
+ import { cacheEconomics } from './usage.js';
23
+ /** Newer-log slices for one label, dearest first. */
24
+ function slicesForLabel(report, label) {
25
+ return report.byLabelAndModel
26
+ .filter((s) => s.label === label && s.breakdown.calls > 0)
27
+ .sort((a, b) => b.breakdown.totalUsd - a.breakdown.totalUsd);
28
+ }
29
+ function attributionFrom(action, after) {
30
+ const before = action.detail.baseline;
31
+ if (before === undefined || after === null)
32
+ return null;
33
+ return {
34
+ calls: { before: before.calls, after: after.calls },
35
+ inputPerCallTokens: { before: before.inputPerCallTokens, after: after.inputPerCallTokens },
36
+ outputPerCallTokens: { before: before.outputPerCallTokens, after: after.outputPerCallTokens },
37
+ };
38
+ }
39
+ function perCall(breakdown) {
40
+ return {
41
+ calls: breakdown.calls,
42
+ inputPerCallTokens: (breakdown.inputTokens + breakdown.cacheReadTokens + breakdown.cacheWriteTokens) / breakdown.calls,
43
+ outputPerCallTokens: breakdown.outputTokens / breakdown.calls,
44
+ };
45
+ }
46
+ export function verifyPlan(plan, report, options) {
47
+ const actions = [];
48
+ for (const action of plan.actions) {
49
+ const slices = slicesForLabel(report, action.label);
50
+ const total = slices.reduce((sum, s) => sum + s.breakdown.totalUsd, 0);
51
+ const allCalls = slices.reduce((sum, s) => sum + s.breakdown.calls, 0);
52
+ /** The label carries no priced traffic any more: nothing can be told. */
53
+ if (slices.length === 0) {
54
+ actions.push({
55
+ action,
56
+ outcome: 'cannot-tell',
57
+ reason: 'workload-vanished',
58
+ observed: {},
59
+ attribution: null,
60
+ gateFailing: false,
61
+ });
62
+ continue;
63
+ }
64
+ if (action.kind === 'route' || action.kind === 'route+batch') {
65
+ const target = action.detail.routeTo;
66
+ const dearest = slices[0];
67
+ const onTarget = slices.find((s) => s.model === target.id);
68
+ const stillOnOld = slices.find((s) => s.model === action.model);
69
+ const moved = dearest.model === target.id;
70
+ const after = perCall(moved ? dearest.breakdown : (stillOnOld ?? dearest).breakdown);
71
+ actions.push({
72
+ action,
73
+ outcome: moved ? 'arrived' : 'not-arrived',
74
+ reason: null,
75
+ observed: {
76
+ dearestModel: dearest.model,
77
+ onTargetUsd: onTarget?.breakdown.totalUsd ?? 0,
78
+ onOldModelUsd: stillOnOld?.breakdown.totalUsd ?? 0,
79
+ labelUsd: total,
80
+ labelCalls: allCalls,
81
+ // The batch half of route+batch cannot be seen in token counts;
82
+ // the rendering names it instead of counting it as arrived.
83
+ ...(action.kind === 'route+batch' ? { batchObservable: 0 } : {}),
84
+ },
85
+ attribution: attributionFrom(action, after),
86
+ gateFailing: !moved,
87
+ });
88
+ continue;
89
+ }
90
+ if (action.kind === 'batch') {
91
+ // Tokens do not say which tier billed them. Not the team's silence,
92
+ // so it cannot fail the gate — but it is said, never assumed arrived.
93
+ actions.push({
94
+ action,
95
+ outcome: 'cannot-tell',
96
+ reason: 'tier-not-recorded',
97
+ observed: { labelUsd: total, labelCalls: allCalls },
98
+ attribution: null,
99
+ gateFailing: false,
100
+ });
101
+ continue;
102
+ }
103
+ const slice = slices.find((s) => s.model === action.model);
104
+ if (action.kind === 'fix-truncation') {
105
+ // The detection needs sessions and timestamps; a log that dropped them
106
+ // reads as "no retries" for the wrong reason, and that must not pass.
107
+ if (!report.hasSessions || report.span === null) {
108
+ actions.push({
109
+ action,
110
+ outcome: 'cannot-tell',
111
+ reason: 'fields-stopped',
112
+ observed: {},
113
+ attribution: attributionFrom(action, slice ? perCall(slice.breakdown) : null),
114
+ gateFailing: true,
115
+ });
116
+ continue;
117
+ }
118
+ const row = report.truncationRetries.find((r) => r.label === action.label && r.model === action.model);
119
+ const newStake = row === undefined ? 0 : row.wastedUsd + row.retryUsd;
120
+ actions.push({
121
+ action,
122
+ outcome: row === undefined ? 'arrived' : 'not-arrived',
123
+ reason: null,
124
+ observed: {
125
+ retryBillUsd: newStake,
126
+ retried: row?.retried ?? 0,
127
+ truncatedCalls: row?.truncatedCalls ?? 0,
128
+ },
129
+ attribution: attributionFrom(action, slice ? perCall(slice.breakdown) : null),
130
+ gateFailing: row !== undefined,
131
+ });
132
+ continue;
133
+ }
134
+ // fix-caching.
135
+ if (slice === undefined) {
136
+ actions.push({
137
+ action,
138
+ outcome: 'cannot-tell',
139
+ reason: 'workload-vanished',
140
+ observed: {},
141
+ attribution: null,
142
+ gateFailing: false,
143
+ });
144
+ continue;
145
+ }
146
+ const economics = cacheEconomics(slice.breakdown);
147
+ if (economics.verdict === 'lost-money' && economics.worstCaseVerdict === economics.verdict) {
148
+ actions.push({
149
+ action,
150
+ outcome: 'not-arrived',
151
+ reason: null,
152
+ observed: { deltaUsd: economics.deltaUsd, spentUsd: economics.spentUsd },
153
+ attribution: attributionFrom(action, perCall(slice.breakdown)),
154
+ gateFailing: true,
155
+ });
156
+ }
157
+ else if (economics.worstCaseVerdict !== economics.verdict) {
158
+ // The verdict cannot settle on this log: the fields that would settle
159
+ // it are not there, and an unverifiable fix is not a verified one.
160
+ actions.push({
161
+ action,
162
+ outcome: 'cannot-tell',
163
+ reason: 'fields-stopped',
164
+ observed: {},
165
+ attribution: attributionFrom(action, perCall(slice.breakdown)),
166
+ gateFailing: true,
167
+ });
168
+ }
169
+ else {
170
+ actions.push({
171
+ action,
172
+ outcome: 'arrived',
173
+ reason: null,
174
+ observed: { deltaUsd: economics.deltaUsd, spentUsd: economics.spentUsd },
175
+ attribution: attributionFrom(action, perCall(slice.breakdown)),
176
+ gateFailing: false,
177
+ });
178
+ }
179
+ }
180
+ return {
181
+ schemaVersion: 1,
182
+ planCreatedAt: plan.createdAt ?? null,
183
+ planPricing: plan.pricingLastReviewed,
184
+ currentPricing: options.currentPricingLastReviewed,
185
+ pricesChanged: plan.pricingLastReviewed !== options.currentPricingLastReviewed,
186
+ actions,
187
+ arrived: actions.filter((a) => a.outcome === 'arrived').length,
188
+ notArrived: actions.filter((a) => a.outcome === 'not-arrived').length,
189
+ cannotTell: actions.filter((a) => a.outcome === 'cannot-tell').length,
190
+ gateFailures: actions.filter((a) => a.gateFailing).length,
191
+ };
192
+ }
193
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trazum/core",
3
- "version": "1.37.0",
3
+ "version": "1.39.0",
4
4
  "description": "Trazum core: priced advisories for LLM prompts (caching, model tier, batching, schemas), plus deterministic trimming, token counting and pricing.",
5
5
  "license": "MIT",
6
6
  "author": "David Mu\u00f1oz Rey",
package/src/index.ts CHANGED
@@ -44,6 +44,10 @@ export { coverageDrift, COVERAGE_FIELDS, COVERAGE_DRIFT_MIN } from './coverage-d
44
44
  export { explainGateFailure, gateMargin, GATE_MARGIN_TIGHT } from './gate-explain.js';
45
45
  export { measuredUsage, labelCoverage, MIN_SCALE_DAYS, SCALE_TO_DAYS } from './measured-profile.js';
46
46
  export { assignSources, fleetRollup } from './fleet.js';
47
+ export { buildPlan, planLabelName } from './plan.js';
48
+ export type { PlanAction, PlanActionKind, PlanAssumption, PlanDocument } from './plan.js';
49
+ export { verifyPlan } from './verify.js';
50
+ export type { CannotTellReason, PlanVerification, VerifiedAction, VerifyOutcome } from './verify.js';
47
51
  export type { FleetSource, FleetRollup } from './fleet.js';
48
52
  export type { MeasuredUsage, LabelCoverage } from './measured-profile.js';
49
53
  export type { GateExplanation } from './gate-explain.js';
package/src/plan.ts ADDED
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Not a list of findings — a ranked, costed, non-additive plan of what to do.
3
+ *
4
+ * The report names findings; a person then decides what to do first by doing
5
+ * arithmetic in their head, and head-arithmetic on savings gets done by
6
+ * *adding* them — which the levers module has documented as wrong since it
7
+ * shipped ($12.60 plus $10.50 against a $21.00 slice). This module does the
8
+ * composition once, correctly, and attaches to every action the things the
9
+ * log cannot confirm, because a plan that hides its assumptions is advice
10
+ * pretending to be arithmetic.
11
+ *
12
+ * **Everything here is derived from figures the report already computed.**
13
+ * Route and batch come from `billLevers` (combined, never summed). The
14
+ * truncation action's stake is the retry bill `truncationRetries` measured.
15
+ * The cache action's stake is `cacheEconomics`' own delta. Nothing is
16
+ * invented, and each action carries how to check the part that is not
17
+ * arithmetic.
18
+ *
19
+ * **The total is stated honestly.** Actions on *different* slices add
20
+ * cleanly; the one composition that does not add — route and batch on the
21
+ * same slice — arrives already combined inside a single action, so the
22
+ * plan's total is a sum of non-overlapping figures by construction. Measured
23
+ * stakes (money already spent on retries, money already lost to caching) are
24
+ * totalled separately from projected savings: "what you would save" and
25
+ * "what you already paid" are different columns, and merging them makes a
26
+ * number that is neither.
27
+ */
28
+
29
+ import { UNLABELLED, cacheEconomics } from './usage.js';
30
+ import type { UsageProfileReport } from './usage.js';
31
+ import type { BillLevers } from './levers.js';
32
+
33
+ export type PlanActionKind = 'route' | 'batch' | 'route+batch' | 'fix-truncation' | 'fix-caching';
34
+
35
+ /**
36
+ * What the log cannot confirm, as data rather than prose.
37
+ *
38
+ * Rendering lives with whoever renders — the CLI localizes these, and 1.39's
39
+ * verification can match them structurally. An English sentence baked in here
40
+ * would be a browser-safe module deciding the reader's language.
41
+ */
42
+ export type PlanAssumption =
43
+ /** The cheaper model can actually do this work — quality, not arithmetic. */
44
+ | { kind: 'model-capability'; model: string }
45
+ /** These calls tolerate a batch window's latency. */
46
+ | { kind: 'batch-window' }
47
+ /** The truncation-retry pairing is real — the log sees shapes, not content. */
48
+ | { kind: 'retry-pattern-real' }
49
+ /** A max_tokens the answers fit inside removes the retry pair. */
50
+ | { kind: 'max-tokens-fits' }
51
+ /** The traffic pattern holds — a cache underwater on this log may pay on other traffic. */
52
+ | { kind: 'traffic-pattern-holds' };
53
+
54
+ export interface PlanAction {
55
+ kind: PlanActionKind;
56
+ /** The workload this acts on. `UNLABELLED` renders as the unlabelled bucket. */
57
+ label: string;
58
+ model: string;
59
+ /**
60
+ * Projected saving per the log's own period, for route/batch — or null for
61
+ * the measured-stake actions, whose money is in `stakeUsd` instead. Never
62
+ * both: a projection and a measurement in one field is a number that is
63
+ * neither.
64
+ */
65
+ savingUsd: number | null;
66
+ /**
67
+ * Money already measured against this problem — the retry bill, the cache
68
+ * loss. Null for the projected actions.
69
+ */
70
+ stakeUsd: number | null;
71
+ /** What the log cannot confirm. Every entry is a human's question to answer. */
72
+ assumes: PlanAssumption[];
73
+ /** How to check the assumption, when a Trazum command can. */
74
+ check: string | null;
75
+ /** What this action does, in one machine-stable keyword per detail. */
76
+ detail: {
77
+ /** Route target, when the action moves the calls. */
78
+ routeTo?: { id: string; displayName: string };
79
+ /** The measured pieces behind a stake. */
80
+ measured?: Record<string, number>;
81
+ /**
82
+ * The slice as it was when the plan was made — what a later verification
83
+ * compares the newer log against. Without this the plan is a prediction
84
+ * with no record of the world it was made in, and "calls doubled" could
85
+ * never be told from "the prediction was wrong".
86
+ */
87
+ baseline?: { calls: number; usd: number; inputPerCallTokens: number; outputPerCallTokens: number };
88
+ };
89
+ }
90
+
91
+ export interface PlanDocument {
92
+ /** Same contract discipline as the profile JSON. */
93
+ schemaVersion: 1;
94
+ /** The period the plan's figures cover, or null when the log had no clock. */
95
+ span: { fromMs: number; toMs: number } | null;
96
+ /** The price table behind every dollar here. */
97
+ pricingLastReviewed: string;
98
+ /** Ranked: largest money first, projected or staked alike. */
99
+ actions: PlanAction[];
100
+ /**
101
+ * Projected savings summed — additive by construction, because same-slice
102
+ * compositions arrive pre-combined in one action.
103
+ */
104
+ projectedSavingUsd: number;
105
+ /** Measured stakes summed: money already paid to problems this plan names. */
106
+ measuredStakeUsd: number;
107
+ /** The bill the plan was made against. */
108
+ totalUsd: number;
109
+ }
110
+
111
+ /**
112
+ * Builds the plan from a report and its levers.
113
+ *
114
+ * `pricingLastReviewed` is passed in rather than imported so the plan records
115
+ * the catalogue that actually priced it — an overlay's date when one was in
116
+ * effect, which 1.39's verification needs to tell "the prediction was wrong"
117
+ * from "the prices changed".
118
+ */
119
+ export function buildPlan(
120
+ report: UsageProfileReport,
121
+ levers: BillLevers,
122
+ pricingLastReviewed: string,
123
+ ): PlanDocument {
124
+ const actions: PlanAction[] = [];
125
+
126
+ /** The slice as the plan saw it, recorded so verification has a "before". */
127
+ const baselineOf = (label: string, model: string) => {
128
+ const slice = report.byLabelAndModel.find((s) => s.label === label && s.model === model);
129
+ if (slice === undefined || slice.breakdown.calls === 0) return undefined;
130
+ const b = slice.breakdown;
131
+ return {
132
+ calls: b.calls,
133
+ usd: b.totalUsd,
134
+ inputPerCallTokens: (b.inputTokens + b.cacheReadTokens + b.cacheWriteTokens) / b.calls,
135
+ outputPerCallTokens: b.outputTokens / b.calls,
136
+ };
137
+ };
138
+
139
+ for (const slice of levers.slices) {
140
+ const assumes: PlanAssumption[] = [];
141
+ let kind: PlanActionKind;
142
+ if (slice.route !== null && slice.batch !== null) {
143
+ kind = 'route+batch';
144
+ assumes.push({ kind: 'model-capability', model: slice.route.candidate.displayName });
145
+ assumes.push({ kind: 'batch-window' });
146
+ } else if (slice.route !== null) {
147
+ kind = 'route';
148
+ assumes.push({ kind: 'model-capability', model: slice.route.candidate.displayName });
149
+ } else if (slice.batch !== null) {
150
+ kind = 'batch';
151
+ assumes.push({ kind: 'batch-window' });
152
+ } else {
153
+ continue;
154
+ }
155
+ actions.push({
156
+ kind,
157
+ label: slice.label,
158
+ model: slice.model,
159
+ savingUsd: slice.combinedUsd,
160
+ stakeUsd: null,
161
+ assumes,
162
+ check: slice.route !== null ? 'trazum route <log> --prompt-file <prompt> --cases <cases>' : null,
163
+ detail: {
164
+ ...(slice.route !== null ? { routeTo: slice.route.candidate } : {}),
165
+ baseline: baselineOf(slice.label, slice.model),
166
+ },
167
+ });
168
+ }
169
+
170
+ for (const row of report.truncationRetries) {
171
+ actions.push({
172
+ kind: 'fix-truncation',
173
+ label: row.label,
174
+ model: row.model,
175
+ savingUsd: null,
176
+ stakeUsd: row.wastedUsd + row.retryUsd,
177
+ assumes: [{ kind: 'retry-pattern-real' }, { kind: 'max-tokens-fits' }],
178
+ check: null,
179
+ detail: {
180
+ measured: {
181
+ wastedUsd: row.wastedUsd,
182
+ retryUsd: row.retryUsd,
183
+ retried: row.retried,
184
+ truncatedCalls: row.truncatedCalls,
185
+ },
186
+ baseline: baselineOf(row.label, row.model),
187
+ },
188
+ });
189
+ }
190
+
191
+ for (const slice of report.byLabelAndModel) {
192
+ const economics = cacheEconomics(slice.breakdown);
193
+ // Only a settled loss becomes an action: an unsettled verdict is a
194
+ // missing field, and "add the field" is the report's advice, not a plan's.
195
+ if (economics.verdict !== 'lost-money' || economics.worstCaseVerdict !== economics.verdict) continue;
196
+ actions.push({
197
+ kind: 'fix-caching',
198
+ label: slice.label,
199
+ model: slice.model,
200
+ savingUsd: null,
201
+ stakeUsd: economics.deltaUsd,
202
+ assumes: [{ kind: 'traffic-pattern-holds' }],
203
+ check: null,
204
+ detail: {
205
+ measured: {
206
+ spentUsd: economics.spentUsd,
207
+ withoutCachingUsd: economics.withoutCachingUsd,
208
+ },
209
+ baseline: baselineOf(slice.label, slice.model),
210
+ },
211
+ });
212
+ }
213
+
214
+ actions.sort(
215
+ (a, b) => (b.savingUsd ?? b.stakeUsd ?? 0) - (a.savingUsd ?? a.stakeUsd ?? 0),
216
+ );
217
+
218
+ return {
219
+ schemaVersion: 1,
220
+ span: report.span === null ? null : { fromMs: report.span.fromMs, toMs: report.span.toMs },
221
+ pricingLastReviewed,
222
+ actions,
223
+ projectedSavingUsd: actions.reduce((sum, a) => sum + (a.savingUsd ?? 0), 0),
224
+ measuredStakeUsd: actions.reduce((sum, a) => sum + (a.stakeUsd ?? 0), 0),
225
+ totalUsd: report.total.totalUsd,
226
+ };
227
+ }
228
+
229
+ /** Renders `UNLABELLED` for humans without leaking the sentinel. */
230
+ export function planLabelName(label: string, unlabelled: string): string {
231
+ return label === UNLABELLED ? unlabelled : label;
232
+ }
package/src/verify.ts ADDED
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Did it work? — the plan held to the log that came after it.
3
+ *
4
+ * Every optimisation tool says what you *would* save; almost none says what
5
+ * you *did*. This module takes a saved plan and a newer report and answers
6
+ * per action, with **three outcomes and never two**: the change arrived, the
7
+ * change did not arrive, or *it cannot be told* — because the workload
8
+ * vanished, the fields the detection needs stopped being recorded, or the
9
+ * log simply cannot see the thing (tokens do not say which tier billed
10
+ * them). The third outcome is the honest one, and the one every other tool
11
+ * renders as the first.
12
+ *
13
+ * **Differences are attributed, not just stated.** A predicted saving that
14
+ * did not appear in the bill is decomposed against the plan's recorded
15
+ * baseline: the calls moved, the output per call moved, the input per call
16
+ * moved — measured ratios with names, never a single number that blames
17
+ * nobody. And a plan priced under one catalogue verified under another says
18
+ * so: the tool must not blame a team for a saving that arithmetic revoked.
19
+ *
20
+ * Browser-safe like everything here: two documents in, one verdict out.
21
+ */
22
+
23
+ import { cacheEconomics } from './usage.js';
24
+ import type { UsageProfileReport } from './usage.js';
25
+ import type { PlanAction, PlanDocument } from './plan.js';
26
+
27
+ export type VerifyOutcome = 'arrived' | 'not-arrived' | 'cannot-tell';
28
+
29
+ /**
30
+ * Why an action cannot be told. The distinction matters to the gate: a
31
+ * workload that vanished is the world's doing; a log that stopped recording
32
+ * the fields the detection needs is the team's, and "not recorded" must not
33
+ * read as "fixed".
34
+ */
35
+ export type CannotTellReason = 'workload-vanished' | 'fields-stopped' | 'tier-not-recorded';
36
+
37
+ export interface VerifiedAction {
38
+ /** The plan's action, verbatim — the prediction being judged. */
39
+ action: PlanAction;
40
+ outcome: VerifyOutcome;
41
+ reason: CannotTellReason | null;
42
+ /**
43
+ * What the newer log measured for this slice, per kind: where the money
44
+ * sits now, the new retry bill, the new cache delta. Keys are stable.
45
+ */
46
+ observed: Record<string, number | string | null>;
47
+ /**
48
+ * The world's movement between the two logs, from the plan's recorded
49
+ * baseline: never a verdict, always the measured before and after.
50
+ */
51
+ attribution: {
52
+ calls?: { before: number; after: number };
53
+ inputPerCallTokens?: { before: number; after: number };
54
+ outputPerCallTokens?: { before: number; after: number };
55
+ } | null;
56
+ /**
57
+ * Whether this action fails `--gate`. `not-arrived` always does;
58
+ * `cannot-tell` does only for `fields-stopped` — a team that degraded its
59
+ * own log must not pass the gate on the strength of the silence. A
60
+ * vanished workload and an unrecordable tier fail nothing.
61
+ */
62
+ gateFailing: boolean;
63
+ }
64
+
65
+ export interface PlanVerification {
66
+ schemaVersion: 1;
67
+ /** When the plan was made, when it carries the stamp. */
68
+ planCreatedAt: string | null;
69
+ /** The catalogue that priced the plan, and the one pricing this check. */
70
+ planPricing: string;
71
+ currentPricing: string;
72
+ /**
73
+ * True when those differ: every dollar comparison here is then two
74
+ * measurements under two price lists, and the rendering must say so
75
+ * rather than let a repricing read as a team's failure.
76
+ */
77
+ pricesChanged: boolean;
78
+ actions: VerifiedAction[];
79
+ arrived: number;
80
+ notArrived: number;
81
+ cannotTell: number;
82
+ gateFailures: number;
83
+ }
84
+
85
+ /** Newer-log slices for one label, dearest first. */
86
+ function slicesForLabel(report: UsageProfileReport, label: string) {
87
+ return report.byLabelAndModel
88
+ .filter((s) => s.label === label && s.breakdown.calls > 0)
89
+ .sort((a, b) => b.breakdown.totalUsd - a.breakdown.totalUsd);
90
+ }
91
+
92
+ function attributionFrom(
93
+ action: PlanAction,
94
+ after: { calls: number; inputPerCallTokens: number; outputPerCallTokens: number } | null,
95
+ ): VerifiedAction['attribution'] {
96
+ const before = action.detail.baseline;
97
+ if (before === undefined || after === null) return null;
98
+ return {
99
+ calls: { before: before.calls, after: after.calls },
100
+ inputPerCallTokens: { before: before.inputPerCallTokens, after: after.inputPerCallTokens },
101
+ outputPerCallTokens: { before: before.outputPerCallTokens, after: after.outputPerCallTokens },
102
+ };
103
+ }
104
+
105
+ function perCall(breakdown: { calls: number; inputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; outputTokens: number }) {
106
+ return {
107
+ calls: breakdown.calls,
108
+ inputPerCallTokens:
109
+ (breakdown.inputTokens + breakdown.cacheReadTokens + breakdown.cacheWriteTokens) / breakdown.calls,
110
+ outputPerCallTokens: breakdown.outputTokens / breakdown.calls,
111
+ };
112
+ }
113
+
114
+ export function verifyPlan(
115
+ plan: PlanDocument & { createdAt?: string },
116
+ report: UsageProfileReport,
117
+ options: { currentPricingLastReviewed: string },
118
+ ): PlanVerification {
119
+ const actions: VerifiedAction[] = [];
120
+
121
+ for (const action of plan.actions) {
122
+ const slices = slicesForLabel(report, action.label);
123
+ const total = slices.reduce((sum, s) => sum + s.breakdown.totalUsd, 0);
124
+ const allCalls = slices.reduce((sum, s) => sum + s.breakdown.calls, 0);
125
+
126
+ /** The label carries no priced traffic any more: nothing can be told. */
127
+ if (slices.length === 0) {
128
+ actions.push({
129
+ action,
130
+ outcome: 'cannot-tell',
131
+ reason: 'workload-vanished',
132
+ observed: {},
133
+ attribution: null,
134
+ gateFailing: false,
135
+ });
136
+ continue;
137
+ }
138
+
139
+ if (action.kind === 'route' || action.kind === 'route+batch') {
140
+ const target = action.detail.routeTo!;
141
+ const dearest = slices[0]!;
142
+ const onTarget = slices.find((s) => s.model === target.id);
143
+ const stillOnOld = slices.find((s) => s.model === action.model);
144
+ const moved = dearest.model === target.id;
145
+ const after = perCall(
146
+ moved ? dearest.breakdown : (stillOnOld ?? dearest).breakdown,
147
+ );
148
+ actions.push({
149
+ action,
150
+ outcome: moved ? 'arrived' : 'not-arrived',
151
+ reason: null,
152
+ observed: {
153
+ dearestModel: dearest.model,
154
+ onTargetUsd: onTarget?.breakdown.totalUsd ?? 0,
155
+ onOldModelUsd: stillOnOld?.breakdown.totalUsd ?? 0,
156
+ labelUsd: total,
157
+ labelCalls: allCalls,
158
+ // The batch half of route+batch cannot be seen in token counts;
159
+ // the rendering names it instead of counting it as arrived.
160
+ ...(action.kind === 'route+batch' ? { batchObservable: 0 } : {}),
161
+ },
162
+ attribution: attributionFrom(action, after),
163
+ gateFailing: !moved,
164
+ });
165
+ continue;
166
+ }
167
+
168
+ if (action.kind === 'batch') {
169
+ // Tokens do not say which tier billed them. Not the team's silence,
170
+ // so it cannot fail the gate — but it is said, never assumed arrived.
171
+ actions.push({
172
+ action,
173
+ outcome: 'cannot-tell',
174
+ reason: 'tier-not-recorded',
175
+ observed: { labelUsd: total, labelCalls: allCalls },
176
+ attribution: null,
177
+ gateFailing: false,
178
+ });
179
+ continue;
180
+ }
181
+
182
+ const slice = slices.find((s) => s.model === action.model);
183
+
184
+ if (action.kind === 'fix-truncation') {
185
+ // The detection needs sessions and timestamps; a log that dropped them
186
+ // reads as "no retries" for the wrong reason, and that must not pass.
187
+ if (!report.hasSessions || report.span === null) {
188
+ actions.push({
189
+ action,
190
+ outcome: 'cannot-tell',
191
+ reason: 'fields-stopped',
192
+ observed: {},
193
+ attribution: attributionFrom(action, slice ? perCall(slice.breakdown) : null),
194
+ gateFailing: true,
195
+ });
196
+ continue;
197
+ }
198
+ const row = report.truncationRetries.find(
199
+ (r) => r.label === action.label && r.model === action.model,
200
+ );
201
+ const newStake = row === undefined ? 0 : row.wastedUsd + row.retryUsd;
202
+ actions.push({
203
+ action,
204
+ outcome: row === undefined ? 'arrived' : 'not-arrived',
205
+ reason: null,
206
+ observed: {
207
+ retryBillUsd: newStake,
208
+ retried: row?.retried ?? 0,
209
+ truncatedCalls: row?.truncatedCalls ?? 0,
210
+ },
211
+ attribution: attributionFrom(action, slice ? perCall(slice.breakdown) : null),
212
+ gateFailing: row !== undefined,
213
+ });
214
+ continue;
215
+ }
216
+
217
+ // fix-caching.
218
+ if (slice === undefined) {
219
+ actions.push({
220
+ action,
221
+ outcome: 'cannot-tell',
222
+ reason: 'workload-vanished',
223
+ observed: {},
224
+ attribution: null,
225
+ gateFailing: false,
226
+ });
227
+ continue;
228
+ }
229
+ const economics = cacheEconomics(slice.breakdown);
230
+ if (economics.verdict === 'lost-money' && economics.worstCaseVerdict === economics.verdict) {
231
+ actions.push({
232
+ action,
233
+ outcome: 'not-arrived',
234
+ reason: null,
235
+ observed: { deltaUsd: economics.deltaUsd, spentUsd: economics.spentUsd },
236
+ attribution: attributionFrom(action, perCall(slice.breakdown)),
237
+ gateFailing: true,
238
+ });
239
+ } else if (economics.worstCaseVerdict !== economics.verdict) {
240
+ // The verdict cannot settle on this log: the fields that would settle
241
+ // it are not there, and an unverifiable fix is not a verified one.
242
+ actions.push({
243
+ action,
244
+ outcome: 'cannot-tell',
245
+ reason: 'fields-stopped',
246
+ observed: {},
247
+ attribution: attributionFrom(action, perCall(slice.breakdown)),
248
+ gateFailing: true,
249
+ });
250
+ } else {
251
+ actions.push({
252
+ action,
253
+ outcome: 'arrived',
254
+ reason: null,
255
+ observed: { deltaUsd: economics.deltaUsd, spentUsd: economics.spentUsd },
256
+ attribution: attributionFrom(action, perCall(slice.breakdown)),
257
+ gateFailing: false,
258
+ });
259
+ }
260
+ }
261
+
262
+ return {
263
+ schemaVersion: 1,
264
+ planCreatedAt: plan.createdAt ?? null,
265
+ planPricing: plan.pricingLastReviewed,
266
+ currentPricing: options.currentPricingLastReviewed,
267
+ pricesChanged: plan.pricingLastReviewed !== options.currentPricingLastReviewed,
268
+ actions,
269
+ arrived: actions.filter((a) => a.outcome === 'arrived').length,
270
+ notArrived: actions.filter((a) => a.outcome === 'not-arrived').length,
271
+ cannotTell: actions.filter((a) => a.outcome === 'cannot-tell').length,
272
+ gateFailures: actions.filter((a) => a.gateFailing).length,
273
+ };
274
+ }