@claudexor/budget 3.0.4 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./ledger.js";
2
2
  export * from "./router.js";
3
3
  export * from "./observe.js";
4
- export { loadHarnessMetrics, recordHarnessMetric, metricsPath, type HarnessMetric, type HarnessMetrics, } from "./metrics.js";
4
+ export { loadHarnessMetrics, recordHarnessMetric } from "./metrics.js";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./ledger.js";
2
2
  export * from "./router.js";
3
3
  export * from "./observe.js";
4
- export { loadHarnessMetrics, recordHarnessMetric, metricsPath, } from "./metrics.js";
4
+ export { loadHarnessMetrics, recordHarnessMetric } from "./metrics.js";
5
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,WAAW,GAGZ,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC"}
package/dist/router.d.ts CHANGED
@@ -1,11 +1,36 @@
1
- import type { BillingKnowledge, CredentialRoute, EffortHint, Intent, PaidFallback, QualityTierSet, RoutingGoal } from "@claudexor/schema";
1
+ import type { AuthVerification, BillingKnowledge, CredentialRoute, EffortHint, Intent, PaidFallback, QualityTierSet, RouteRankingRationale, RoutingGoal } from "@claudexor/schema";
2
2
  import type { BudgetLedger } from "./ledger.js";
3
+ /**
4
+ * The typed auth-route evidence a route ran (or would run) under — the doctor's
5
+ * `auth_sources` verification for the credential source plus the concrete
6
+ * `credential_route` receipt. This is the ONLY sanctioned source of billing
7
+ * knowledge (INV-060 / schema `auth.ts`: a credential transport alone never
8
+ * proves entitlement or zero cost, but a VERIFIED native/subscription route is
9
+ * a subscription-entitlement fact). No per-vendor price table is inferred.
10
+ */
11
+ export interface RouteAuthEvidence {
12
+ route: CredentialRoute | null;
13
+ verification: AuthVerification;
14
+ }
15
+ /**
16
+ * Billing knowledge derived STRICTLY from the typed auth route (QA-034). A
17
+ * verified vendor-native route proves `subscription_entitlement`; a managed
18
+ * API-key route is `metered` and is NEVER presumed free; every other route
19
+ * (unverified native, credential-free local, unknown) stays `unknown`. This is
20
+ * the missing production producer the schema/router already knew how to consume.
21
+ */
22
+ export declare function billingKnowledgeForAuthRoute(evidence: RouteAuthEvidence): BillingKnowledge;
3
23
  export interface RouterCandidate {
4
24
  harnessId: string;
5
25
  available: boolean;
6
26
  model?: string;
7
27
  effort?: EffortHint;
8
28
  billingKnowledge?: BillingKnowledge;
29
+ /** Typed auth-route evidence. When present it is AUTHORITATIVE for billing
30
+ * knowledge (derived via billingKnowledgeForAuthRoute), so a verified native
31
+ * route survives paid_fallback:never and ranks with a real economy tuple.
32
+ * Falls back to the explicit billingKnowledge (else unknown) when absent. */
33
+ authRoute?: RouteAuthEvidence;
9
34
  incrementalCostUsd?: number | null;
10
35
  credentialRoute?: CredentialRoute;
11
36
  /** Effective credential subject for quota filtering (release wave round-16
@@ -20,11 +45,27 @@ export interface RouteContext {
20
45
  intent: Intent;
21
46
  qualityTiers: QualityTierSet;
22
47
  ledger: BudgetLedger;
48
+ /**
49
+ * The instant EVERY clock-dependent axis is evaluated at (cooldown expiry and
50
+ * quota pace slack). Optional: each entry point pins one `Date.now()` for the
51
+ * whole call when it is absent. A caller that ranks a pool and then explains
52
+ * the SAME pool must pass a pinned instant so both passes read one clock.
53
+ */
54
+ now?: number;
23
55
  }
24
56
  export declare class RoutingPreflightError extends Error {
25
57
  readonly code = "routing_preflight_refused";
26
58
  }
27
59
  /** Order candidates transparently; lower tuple values win. Unknown quota remains eligible. */
28
- export declare function rankHarnesses(candidates: RouterCandidate[], ctx: RouteContext): RouterCandidate[];
60
+ export declare function rankHarnesses(candidates: RouterCandidate[], context: RouteContext): RouterCandidate[];
29
61
  export declare function selectHarness(candidates: RouterCandidate[], ctx: RouteContext): RouterCandidate | null;
62
+ /**
63
+ * Explain a ranking as a typed record (QA-034): the per-candidate billing/cost
64
+ * tuple, the eligible/dropped split, and the DECISIVE reason. Kept axis-aligned
65
+ * with rankHarnesses so the recorded rationale can never disagree with the
66
+ * order actually taken. Prose-free; a surface projects it, never reconstructs it.
67
+ * The shape is the schema-owned RouteRankingRationale (snake_case) so it can be
68
+ * persisted verbatim as RunTelemetry.routing_rationale.
69
+ */
70
+ export declare function explainRanking(candidates: RouterCandidate[], context: RouteContext): RouteRankingRationale;
30
71
  //# sourceMappingURL=router.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,UAAU,EACV,MAAM,EACN,YAAY,EACZ,cAAc,EACd,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;qEAGiE;IACjE,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,YAAY,EAAE,YAAY,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,cAAc,CAAC;IAC7B,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,+BAA+B;CAC7C;AAqCD,8FAA8F;AAC9F,wBAAgB,aAAa,CAAC,UAAU,EAAE,eAAe,EAAE,EAAE,GAAG,EAAE,YAAY,GAAG,eAAe,EAAE,CAmCjG;AAED,wBAAgB,aAAa,CAC3B,UAAU,EAAE,eAAe,EAAE,EAC7B,GAAG,EAAE,YAAY,GAChB,eAAe,GAAG,IAAI,CAExB"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,UAAU,EACV,MAAM,EACN,YAAY,EACZ,cAAc,EAEd,qBAAqB,EAErB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,gBAAgB,CAAC;CAChC;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,iBAAiB,GAAG,gBAAgB,CAM1F;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC;;;iFAG6E;IAC7E,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;qEAGiE;IACjE,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,YAAY,EAAE,YAAY,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,cAAc,CAAC;IAC7B,MAAM,EAAE,YAAY,CAAC;IACrB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,+BAA+B;CAC7C;AAkHD,8FAA8F;AAC9F,wBAAgB,aAAa,CAC3B,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,EAAE,YAAY,GACpB,eAAe,EAAE,CAwBnB;AAED,wBAAgB,aAAa,CAC3B,UAAU,EAAE,eAAe,EAAE,EAC7B,GAAG,EAAE,YAAY,GAChB,eAAe,GAAG,IAAI,CAExB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,EAAE,YAAY,GACpB,qBAAqB,CAgBvB"}
package/dist/router.js CHANGED
@@ -1,3 +1,18 @@
1
+ /**
2
+ * Billing knowledge derived STRICTLY from the typed auth route (QA-034). A
3
+ * verified vendor-native route proves `subscription_entitlement`; a managed
4
+ * API-key route is `metered` and is NEVER presumed free; every other route
5
+ * (unverified native, credential-free local, unknown) stays `unknown`. This is
6
+ * the missing production producer the schema/router already knew how to consume.
7
+ */
8
+ export function billingKnowledgeForAuthRoute(evidence) {
9
+ if (evidence.route === "managed_api_key")
10
+ return "metered";
11
+ if (evidence.route === "vendor_native" && evidence.verification === "passed") {
12
+ return "subscription_entitlement";
13
+ }
14
+ return "unknown";
15
+ }
1
16
  export class RoutingPreflightError extends Error {
2
17
  code = "routing_preflight_refused";
3
18
  }
@@ -8,12 +23,30 @@ function tierIndex(candidate, ctx) {
8
23
  route.effort === candidate.effort));
9
24
  return index < 0 ? null : index;
10
25
  }
26
+ /** Effective billing knowledge: the typed auth route is authoritative when
27
+ * present (QA-034), else the explicitly supplied value, else unknown. */
28
+ function effectiveBilling(candidate) {
29
+ if (candidate.authRoute)
30
+ return billingKnowledgeForAuthRoute(candidate.authRoute);
31
+ return candidate.billingKnowledge ?? "unknown";
32
+ }
11
33
  function isIncrementalPaid(candidate) {
12
- return !["proven_zero", "subscription_entitlement"].includes(candidate.billingKnowledge ?? "unknown");
34
+ return !["proven_zero", "subscription_entitlement"].includes(effectiveBilling(candidate));
35
+ }
36
+ /**
37
+ * Pin ONE evaluation instant for a whole ranking pass. Reading the clock again
38
+ * per candidate makes the comparator time-varying: two candidates carrying
39
+ * IDENTICAL quota observations get slacks that differ by whatever milliseconds
40
+ * elapsed between the two reads, so a pool nothing actually separates is
41
+ * ordered by scheduler jitter and reports `expiring_quota_slack` instead of the
42
+ * honest `declared_order`. The pin is what makes equal inputs compare equal.
43
+ */
44
+ function pinInstant(ctx) {
45
+ return ctx.now === undefined ? { ...ctx, now: Date.now() } : ctx;
13
46
  }
14
47
  function eligible(candidates, ctx) {
15
48
  const ready = candidates.filter((candidate) => candidate.available &&
16
- !ctx.ledger.cooldownActive(candidate.harnessId, candidate.credentialRoute, candidate.credentialSubjectId));
49
+ !ctx.ledger.cooldownActive(candidate.harnessId, candidate.credentialRoute, candidate.credentialSubjectId, ctx.now));
17
50
  const free = ready.filter((candidate) => !isIncrementalPaid(candidate));
18
51
  if (ctx.paidFallback === "never")
19
52
  return free;
@@ -21,36 +54,171 @@ function eligible(candidates, ctx) {
21
54
  return free;
22
55
  return ready;
23
56
  }
57
+ /** User-declared tier index the comparators order by (missing => MAX). Shared
58
+ * by the sort and the traced comparator so a "tier" claim is never re-derived. */
59
+ function tierValueOf(candidate, ctx) {
60
+ return tierIndex(candidate, ctx) ?? Number.MAX_SAFE_INTEGER;
61
+ }
62
+ /** Binding pace slack the `auto` comparator orders by; null = none observed.
63
+ * Evaluated at the pass-wide pinned instant (`ctx.now`) — never at a freshly
64
+ * read clock, which would make two identical quota states differ. */
65
+ function autoSlack(candidate, ctx) {
66
+ return ctx.ledger.bindingPaceSlack(candidate.harnessId, candidate.credentialRoute, candidate.credentialSubjectId, ctx.now);
67
+ }
68
+ /** null slack collapses to this so a slack-carrying candidate always outranks a
69
+ * slack-less one, exactly as the comparator subtraction does. */
70
+ const AUTO_SLACK_ABSENT = Number.NEGATIVE_INFINITY;
71
+ /**
72
+ * The SINGLE traced comparator for `auto` (round-4 #3 — this rationale has been
73
+ * wrong three times). It returns both the pair ORDER and the AXIS that produced
74
+ * it, and is the only place the auto ordering is defined. `rankHarnesses` sorts
75
+ * by `.order`; `rankingReason` reads `.axis`. Deriving the recorded reason from
76
+ * the very comparator that ordered the pool makes it structurally impossible for
77
+ * the rationale to claim a factor the sort did not use (the axis-registry
78
+ * pattern used in arbitration, specialized to auto's conditional slack axis).
79
+ *
80
+ * The slack axis is in force whenever EITHER candidate has a non-null slack; the
81
+ * subtraction of the (null => -Infinity) effective values is what decides — so
82
+ * two EQUAL non-null slacks yield order 0 and a null axis (declared order, and
83
+ * the sort does NOT fall through to tiers). Only when BOTH slacks are absent
84
+ * does the comparator compare tier indices.
85
+ *
86
+ * Both slacks are read at the pass-wide pinned instant (`ctx.now`), so the
87
+ * subtraction compares two quota states and nothing else. Re-reading the clock
88
+ * per candidate made the comparator time-varying and broke exactly the
89
+ * guarantee above: equal quota states produced a non-zero order.
90
+ */
91
+ function compareAutoTraced(a, b, ctx) {
92
+ const aSlack = autoSlack(a, ctx);
93
+ const bSlack = autoSlack(b, ctx);
94
+ if (aSlack !== null || bSlack !== null) {
95
+ const order = (bSlack ?? AUTO_SLACK_ABSENT) - (aSlack ?? AUTO_SLACK_ABSENT);
96
+ return { order, axis: order !== 0 ? "expiring_quota_slack" : null };
97
+ }
98
+ const order = tierValueOf(a, ctx) - tierValueOf(b, ctx);
99
+ return { order, axis: order !== 0 ? "quality_tier" : null };
100
+ }
24
101
  /** Order candidates transparently; lower tuple values win. Unknown quota remains eligible. */
25
- export function rankHarnesses(candidates, ctx) {
102
+ export function rankHarnesses(candidates, context) {
103
+ const ctx = pinInstant(context);
26
104
  const routes = eligible(candidates, ctx);
27
105
  if (ctx.goal === "quality" && routes.every((candidate) => tierIndex(candidate, ctx) === null)) {
28
106
  throw new RoutingPreflightError(`quality routing requires a comparable user-declared tier for intent '${ctx.intent}'`);
29
107
  }
108
+ if (ctx.goal === "auto") {
109
+ return routes.toSorted((a, b) => compareAutoTraced(a, b, ctx).order);
110
+ }
30
111
  return routes.toSorted((a, b) => {
31
- const aTier = tierIndex(a, ctx) ?? Number.MAX_SAFE_INTEGER;
32
- const bTier = tierIndex(b, ctx) ?? Number.MAX_SAFE_INTEGER;
112
+ const aTier = tierValueOf(a, ctx);
113
+ const bTier = tierValueOf(b, ctx);
33
114
  if (ctx.goal === "quality")
34
115
  return aTier - bTier;
35
- if (ctx.goal === "economy") {
36
- const aPaid = isIncrementalPaid(a) ? 1 : 0;
37
- const bPaid = isIncrementalPaid(b) ? 1 : 0;
38
- const paidOrder = aPaid - bPaid;
39
- if (paidOrder !== 0)
40
- return paidOrder;
41
- const aCost = a.incrementalCostUsd ?? Number.POSITIVE_INFINITY;
42
- const bCost = b.incrementalCostUsd ?? Number.POSITIVE_INFINITY;
43
- return aCost - bCost || aTier - bTier;
44
- }
45
- const aSlack = ctx.ledger.bindingPaceSlack(a.harnessId, a.credentialRoute, a.credentialSubjectId);
46
- const bSlack = ctx.ledger.bindingPaceSlack(b.harnessId, b.credentialRoute, b.credentialSubjectId);
47
- if (aSlack !== null || bSlack !== null) {
48
- return (bSlack ?? Number.NEGATIVE_INFINITY) - (aSlack ?? Number.NEGATIVE_INFINITY);
49
- }
50
- return aTier - bTier;
116
+ // economy
117
+ const aPaid = isIncrementalPaid(a) ? 1 : 0;
118
+ const bPaid = isIncrementalPaid(b) ? 1 : 0;
119
+ const paidOrder = aPaid - bPaid;
120
+ if (paidOrder !== 0)
121
+ return paidOrder;
122
+ const aCost = a.incrementalCostUsd ?? Number.POSITIVE_INFINITY;
123
+ const bCost = b.incrementalCostUsd ?? Number.POSITIVE_INFINITY;
124
+ return aCost - bCost || aTier - bTier;
51
125
  });
52
126
  }
53
127
  export function selectHarness(candidates, ctx) {
54
128
  return rankHarnesses(candidates, ctx)[0] ?? null;
55
129
  }
130
+ /**
131
+ * Explain a ranking as a typed record (QA-034): the per-candidate billing/cost
132
+ * tuple, the eligible/dropped split, and the DECISIVE reason. Kept axis-aligned
133
+ * with rankHarnesses so the recorded rationale can never disagree with the
134
+ * order actually taken. Prose-free; a surface projects it, never reconstructs it.
135
+ * The shape is the schema-owned RouteRankingRationale (snake_case) so it can be
136
+ * persisted verbatim as RunTelemetry.routing_rationale.
137
+ */
138
+ export function explainRanking(candidates, context) {
139
+ // ONE instant for both passes: the sort and the reason walk must read the same
140
+ // clock, or the recorded rationale can disagree with the order it explains.
141
+ const ctx = pinInstant(context);
142
+ const rankedCandidates = rankHarnesses(candidates, ctx);
143
+ const order = rankedCandidates.map((c) => c.harnessId);
144
+ const eligibleIds = new Set(order);
145
+ const entries = candidates.map((c) => ({
146
+ harness_id: c.harnessId,
147
+ billing_knowledge: effectiveBilling(c),
148
+ incremental_cost_usd: c.incrementalCostUsd ?? null,
149
+ eligible: eligibleIds.has(c.harnessId),
150
+ }));
151
+ const dropped = candidates.filter((c) => !eligibleIds.has(c.harnessId)).map((c) => c.harnessId);
152
+ const reason = rankingReason(ctx, rankedCandidates, candidates);
153
+ return { goal: ctx.goal, paid_fallback: ctx.paidFallback, order, dropped, reason, entries };
154
+ }
155
+ /** Do the ranked candidates carry two distinct user-declared tier indices? Only
156
+ * then did the tier comparator (the auto/economy tie-break) actually decide the
157
+ * order; a single candidate or a uniform tier index leaves the declared order. */
158
+ function tiersDistinguish(ranked, ctx) {
159
+ const indices = ranked.map((c) => tierIndex(c, ctx) ?? Number.MAX_SAFE_INTEGER);
160
+ return indices.some((value) => value !== indices[0]);
161
+ }
162
+ /** Do the ranked candidates carry two distinct incremental costs (with the SAME
163
+ * missing-cost fallback the economy sort uses)? Only then did the cost
164
+ * comparator — not the paid/free split above it or the tier tie-break below —
165
+ * actually decide the order. */
166
+ function costsDistinguish(ranked) {
167
+ const costs = ranked.map((c) => c.incrementalCostUsd ?? Number.POSITIVE_INFINITY);
168
+ return costs.some((value) => value !== costs[0]);
169
+ }
170
+ /**
171
+ * The DECISIVE comparator axis. For `auto` the reason is read directly off the
172
+ * SAME traced comparator that ordered the pool (`compareAutoTraced`) — never
173
+ * re-derived from a separately-recomputed condition (round-4 #3, the third
174
+ * recurrence of a rationale that disagreed with `rankHarnesses`): the reason is
175
+ * whichever axis actually separated an adjacent ranked pair, and `declared_order`
176
+ * when the comparator broke no pair (e.g. equal non-null slacks — which do NOT
177
+ * fall through to tiers).
178
+ */
179
+ function rankingReason(ctx, ranked, candidates) {
180
+ if (ctx.goal === "quality")
181
+ return "quality_tier";
182
+ if (ctx.goal === "auto") {
183
+ // Walk the ranked order through the very comparator that produced it. Since
184
+ // the pool is sorted, any two distinct axis values leave a differing
185
+ // adjacent pair, so this sees every axis the sort actually used.
186
+ const axes = new Set();
187
+ for (let i = 0; i + 1 < ranked.length; i++) {
188
+ const traced = compareAutoTraced(ranked[i], ranked[i + 1], ctx);
189
+ if (traced.axis)
190
+ axes.add(traced.axis);
191
+ }
192
+ if (axes.has("expiring_quota_slack"))
193
+ return "expiring_quota_slack";
194
+ if (axes.has("quality_tier"))
195
+ return "quality_tier";
196
+ return "declared_order";
197
+ }
198
+ // economy: mirror the economy sort tuple EXACTLY — (isIncrementalPaid,
199
+ // incrementalCostUsd, tierIndex) — and report the FIRST axis that actually
200
+ // SEPARATED the pool. Reporting entitlement whenever ANY route is entitled was
201
+ // wrong when they are ALL entitled and cost/tier did the deciding (the same
202
+ // correction made for `auto` in 1d6a2a50).
203
+ const someEntitled = ranked.some((c) => !isIncrementalPaid(c));
204
+ // Entitlement decided when the pool actually WEIGHED an entitled route against
205
+ // a paid one — whether the paid route was ranked BELOW (sort axis 1) or DROPPED
206
+ // by paid_fallback (both are the entitled-preferred-over-paid outcome). When
207
+ // every route CONSIDERED is entitled, entitlement did not decide — cost/tier
208
+ // did (the round-3 correction; matches the pool-wide `auto` mirror in
209
+ // 1d6a2a50). The full candidate set (not just the eligible pool) is consulted
210
+ // so a paid route dropped by paid_fallback:never still attributes the outcome.
211
+ const anyPaidConsidered = candidates.some((c) => isIncrementalPaid(c));
212
+ if (someEntitled && anyPaidConsidered)
213
+ return "subscription_entitlement_first";
214
+ if (costsDistinguish(ranked))
215
+ return "lowest_incremental_cash";
216
+ if (tiersDistinguish(ranked, ctx))
217
+ return "quality_tier";
218
+ // Nothing separated the pool: distinguish "we could not compare cash at all"
219
+ // from a genuine declared-order tie.
220
+ if (ranked.length > 0 && ranked.some((c) => c.incrementalCostUsd == null))
221
+ return "all_incremental_cash_unknown";
222
+ return "declared_order";
223
+ }
56
224
  //# sourceMappingURL=router.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudexor/budget",
3
- "version": "3.0.4",
3
+ "version": "3.1.1",
4
4
  "license": "MIT",
5
5
  "description": "Budget governor: pre-call leases, circuit breaker, evidence-based routing goals, and multi-window quota pacing.",
6
6
  "type": "module",
@@ -16,8 +16,8 @@
16
16
  "dist"
17
17
  ],
18
18
  "dependencies": {
19
- "@claudexor/schema": "3.0.4",
20
- "@claudexor/util": "3.0.4"
19
+ "@claudexor/schema": "3.1.1",
20
+ "@claudexor/util": "3.1.1"
21
21
  },
22
22
  "repository": {
23
23
  "type": "git",