@bymax-one/nest-ai-tokens 1.0.2 → 1.0.4

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/CHANGELOG.md CHANGED
@@ -8,6 +8,57 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [1.0.4] - 2026-08-10
12
+
13
+ Remediation of a local audit's settlement findings (merged in #65). No API changed.
14
+
15
+ ### Fixed
16
+
17
+ - **Negative usage can no longer credit the wallet or budget.** `clampUsageCounts` floors every
18
+ token count — and the provider-reported cost (`providerReportedCostNanoUsd`) — at zero before the
19
+ signed settlement leg. A normalizer that computes a field by subtraction (OpenRouter's
20
+ `prompt - cached`), or a provider that reports a negative `usage.cost`, would otherwise credit
21
+ back more than was reserved and drive budget consumption negative.
22
+ - **Streaming captures are clamped like non-stream captures.** `finalizeCaptureUsage` now routes
23
+ the `StreamUsageCollector` final through `normalizeCaptureUsage`, so a provider-final stream
24
+ carrying negative counts or cost no longer bypasses the floor and reaches settlement raw.
25
+
26
+ ### Documentation
27
+
28
+ - `RecordInput.occurredAt` documents its trust model: it is a deliberate backfill feature, honoured
29
+ as given because `record()`'s input is trusted host input, and a host that forwards a value an
30
+ untrusted end user controls must validate it first — a back-dated timestamp selects a historical
31
+ price and an earlier budget window.
32
+
33
+ ## [1.0.3] - 2026-08-06
34
+
35
+ ### Fixed
36
+
37
+ - **Every suppression reason was dropped from the mutation report.** All 249 `// Stryker
38
+ disable` directives in the source wrote their justification after `--`. Stryker reads a
39
+ directive with one regular expression, `/^\s?Stryker (disable|restore)(?: (next-line))?
40
+ ([a-zA-Z, ]+)(?::(.+)?)?/`, whose mutator list accepts letters, commas and spaces only
41
+ and which captures the reason exclusively after a colon. A `-` therefore closed the
42
+ mutator list and left the reason unmatched, so each directive still silenced its mutant
43
+ but the report recorded Stryker's fallback text, `Ignored using a comment`. The README
44
+ claimed the opposite — that every suppression carries its reason — and documented the
45
+ broken separator as the convention. The directives now use `<Mutator>: <reason>`, which
46
+ is the grammar the parser accepts, and the reasons reach the report.
47
+
48
+ ### Added
49
+
50
+ - `check:mutants` gate (`scripts/check-mutation-directives.mjs`) — validates every
51
+ `// Stryker` comment against the parser's own regular expression, rejecting a reason
52
+ written after `--`, a reason wrapped onto a second comment line (the report keeps only
53
+ the first fragment), and a mutator name Stryker does not know, which would silence
54
+ nothing at all. Wired into CI and `prepublishOnly`. Stryker warns about the last case,
55
+ but only during a mutation run, which on this repo happens post-merge.
56
+
57
+ ### Changed
58
+
59
+ - No runtime behaviour changed. `dist/` differs only in the text of those comments; the
60
+ server bundle moved from 38,096 to 38,145 B brotli, well inside its 40,000 B budget.
61
+
11
62
  ## [1.0.2] - 2026-08-05
12
63
 
13
64
  ### Fixed
@@ -164,4 +215,6 @@ have regressed from. They are kept because the reasoning is worth having.
164
215
  [1.0.0]: https://github.com/bymaxone/nest-ai-tokens/releases/tag/v1.0.0
165
216
  [1.0.1]: https://github.com/bymaxone/nest-ai-tokens/compare/v1.0.0...v1.0.1
166
217
  [1.0.2]: https://github.com/bymaxone/nest-ai-tokens/compare/v1.0.1...v1.0.2
167
- [Unreleased]: https://github.com/bymaxone/nest-ai-tokens/compare/v1.0.2...HEAD
218
+ [1.0.4]: https://github.com/bymaxone/nest-ai-tokens/compare/v1.0.3...v1.0.4
219
+ [1.0.3]: https://github.com/bymaxone/nest-ai-tokens/compare/v1.0.2...v1.0.3
220
+ [Unreleased]: https://github.com/bymaxone/nest-ai-tokens/compare/v1.0.4...HEAD
package/README.md CHANGED
@@ -742,9 +742,11 @@ tests pass".
742
742
  system does, `check:runtime` loads every subpath from the packed tarball in ESM and
743
743
  CommonJS, and `check:published` compiles this README's snippets against `dist/`
744
744
  - ✅ **Every suppression carries its reason** — no coverage directives anywhere; each
745
- `// Stryker disable` in the production source states, after `--`, why the mutant it
746
- silences is unobservable from a unit test (an internal error context, a provider id
747
- reached only through integration)
745
+ `// Stryker disable` in the production source states, after the `:` Stryker reads it
746
+ from, why the mutant it silences is unobservable from a unit test (an internal error
747
+ context, a provider id reached only through integration). `check:mutants` proves the
748
+ reasons parse, so they reach the mutation report instead of being replaced by
749
+ Stryker's `Ignored using a comment` fallback
748
750
 
749
751
  ```bash
750
752
  pnpm test # unit tests
@@ -276,7 +276,7 @@ function computePayloadHash(record) {
276
276
  // src/server/utils/hash-chain.ts
277
277
  function chainHash(prevHash, record) {
278
278
  return deriveIdempotencyKey({
279
- // Stryker disable next-line StringLiteral -- empty-string fallback for null prevHash; tests only verify hash determinism and inequality, not the specific hash value for a genesis block
279
+ // Stryker disable next-line StringLiteral: empty-string fallback for null prevHash; tests only verify hash determinism and inequality, not the specific hash value for a genesis block
280
280
  prevHash: prevHash ?? "",
281
281
  id: record.id,
282
282
  content: computePayloadHash(record)
@@ -274,7 +274,7 @@ function computePayloadHash(record) {
274
274
  // src/server/utils/hash-chain.ts
275
275
  function chainHash(prevHash, record) {
276
276
  return deriveIdempotencyKey({
277
- // Stryker disable next-line StringLiteral -- empty-string fallback for null prevHash; tests only verify hash determinism and inequality, not the specific hash value for a genesis block
277
+ // Stryker disable next-line StringLiteral: empty-string fallback for null prevHash; tests only verify hash determinism and inequality, not the specific hash value for a genesis block
278
278
  prevHash: prevHash ?? "",
279
279
  id: record.id,
280
280
  content: computePayloadHash(record)
@@ -362,9 +362,9 @@ function normalizeMistralUsage(raw) {
362
362
  provider: "mistral",
363
363
  model: str(response.model) ?? "",
364
364
  operation: "chat",
365
- // Stryker disable next-line StringLiteral -- provider and field names in error messages are internal diagnostics
365
+ // Stryker disable next-line StringLiteral: provider and field names in error messages are internal diagnostics
366
366
  inputTokens: requireNum(usage.prompt_tokens, "mistral", "usage.prompt_tokens"),
367
- // Stryker disable next-line StringLiteral -- provider and field names in error messages are internal diagnostics
367
+ // Stryker disable next-line StringLiteral: provider and field names in error messages are internal diagnostics
368
368
  outputTokens: requireNum(usage.completion_tokens, "mistral", "usage.completion_tokens"),
369
369
  raw: usage
370
370
  });
@@ -808,7 +808,7 @@ function validatePricing(options) {
808
808
  if (cacheTtlMs === void 0) return;
809
809
  if (!Number.isInteger(cacheTtlMs) || cacheTtlMs <= 0) {
810
810
  throw invalidConfig(
811
- // Stryker disable next-line StringLiteral -- error reason template is internal diagnostics
811
+ // Stryker disable next-line StringLiteral: error reason template is internal diagnostics
812
812
  `pricing.cacheTtlMs must be a positive integer number of milliseconds, received ${String(cacheTtlMs)}`
813
813
  );
814
814
  }
@@ -1128,7 +1128,7 @@ exports.BudgetGuard = class BudgetGuard {
1128
1128
  this.reflector = reflector;
1129
1129
  if (options.scopeResolver === void 0) {
1130
1130
  throw new AiTokensException("AI_TOKENS_INVALID_CONFIG", void 0, {
1131
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
1131
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
1132
1132
  reason: "BudgetGuard requires options.scopeResolver to resolve the caller scope"
1133
1133
  });
1134
1134
  }
@@ -1359,7 +1359,7 @@ function computePayloadHash(record) {
1359
1359
  // src/server/utils/hash-chain.ts
1360
1360
  function chainHash(prevHash, record) {
1361
1361
  return deriveIdempotencyKey({
1362
- // Stryker disable next-line StringLiteral -- empty-string fallback for null prevHash; tests only verify hash determinism and inequality, not the specific hash value for a genesis block
1362
+ // Stryker disable next-line StringLiteral: empty-string fallback for null prevHash; tests only verify hash determinism and inequality, not the specific hash value for a genesis block
1363
1363
  prevHash: prevHash ?? "",
1364
1364
  id: record.id,
1365
1365
  content: computePayloadHash(record)
@@ -1560,7 +1560,7 @@ exports.LedgerService = class LedgerService {
1560
1560
  const original = await this.store.findById(recordId);
1561
1561
  if (original?.status !== "posted") {
1562
1562
  throw new AiTokensException("AI_TOKENS_IDEMPOTENCY_CONFLICT", void 0, {
1563
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
1563
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
1564
1564
  reason: original === null ? "record not found" : `cannot reverse a ${original.status} record`,
1565
1565
  recordId,
1566
1566
  requestedReason: reason
@@ -1581,7 +1581,7 @@ exports.LedgerService = class LedgerService {
1581
1581
  assertLegalTransition(from2, to, patch) {
1582
1582
  if (!isLegalLedgerTransition(from2, to)) {
1583
1583
  throw new AiTokensException("AI_TOKENS_IDEMPOTENCY_CONFLICT", void 0, {
1584
- // Stryker disable next-line StringLiteral -- error reason template is internal diagnostics
1584
+ // Stryker disable next-line StringLiteral: error reason template is internal diagnostics
1585
1585
  reason: `illegal transition ${from2} \u2192 ${to}`
1586
1586
  });
1587
1587
  }
@@ -1589,7 +1589,7 @@ exports.LedgerService = class LedgerService {
1589
1589
  for (const key of Object.keys(patch)) {
1590
1590
  if (!isLegalTransitionPatchKey(from2, to, key)) {
1591
1591
  throw new AiTokensException("AI_TOKENS_IDEMPOTENCY_CONFLICT", void 0, {
1592
- // Stryker disable next-line StringLiteral -- error reason template is internal diagnostics
1592
+ // Stryker disable next-line StringLiteral: error reason template is internal diagnostics
1593
1593
  reason: `${from2} \u2192 ${to} may not patch "${key}"`
1594
1594
  });
1595
1595
  }
@@ -1608,13 +1608,13 @@ function buildCompensatingRecord(original) {
1608
1608
  return {
1609
1609
  tenantId: original.tenantId,
1610
1610
  scope: original.scope,
1611
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { beneficiary: undefined } is equivalent to {} because downstream consumers check !== undefined
1611
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { beneficiary: undefined } is equivalent to {} because downstream consumers check !== undefined
1612
1612
  ...original.beneficiary !== void 0 ? { beneficiary: original.beneficiary } : {},
1613
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { requestedBy: undefined } is equivalent to {}
1613
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { requestedBy: undefined } is equivalent to {}
1614
1614
  ...original.requestedBy !== void 0 ? { requestedBy: original.requestedBy } : {},
1615
1615
  provider: original.provider,
1616
1616
  model: original.model,
1617
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { requestedModel: undefined } is equivalent to {}
1617
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { requestedModel: undefined } is equivalent to {}
1618
1618
  ...original.requestedModel !== void 0 ? { requestedModel: original.requestedModel } : {},
1619
1619
  operation: original.operation,
1620
1620
  serviceTier: original.serviceTier,
@@ -1630,7 +1630,7 @@ function buildCompensatingRecord(original) {
1630
1630
  audioOutTokens: -original.audioOutTokens,
1631
1631
  imageInTokens: -original.imageInTokens,
1632
1632
  imageOutTokens: -original.imageOutTokens,
1633
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { extraUnits: undefined } is equivalent to {} because consumers check !== undefined
1633
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { extraUnits: undefined } is equivalent to {} because consumers check !== undefined
1634
1634
  ...original.extraUnits !== void 0 ? { extraUnits: negateUnits(original.extraUnits) } : {},
1635
1635
  priceVersionId: original.priceVersionId,
1636
1636
  rawCostNanoUsd: -original.rawCostNanoUsd,
@@ -1641,12 +1641,12 @@ function buildCompensatingRecord(original) {
1641
1641
  priceMissing: original.priceMissing,
1642
1642
  status: "posted",
1643
1643
  reversesRecordId: original.id,
1644
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { correlationId: undefined } is equivalent to {}
1644
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { correlationId: undefined } is equivalent to {}
1645
1645
  ...original.correlationId !== void 0 ? { correlationId: original.correlationId } : {},
1646
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { requestId: undefined } is equivalent to {}
1646
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { requestId: undefined } is equivalent to {}
1647
1647
  ...original.requestId !== void 0 ? { requestId: original.requestId } : {},
1648
1648
  isSystemCost: original.isSystemCost,
1649
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { systemCostCategory: undefined } is equivalent to {}
1649
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { systemCostCategory: undefined } is equivalent to {}
1650
1650
  ...original.systemCostCategory !== void 0 ? { systemCostCategory: original.systemCostCategory } : {},
1651
1651
  enforced: original.enforced,
1652
1652
  occurredAt: original.occurredAt
@@ -1683,7 +1683,7 @@ var MarkupResolver = class {
1683
1683
  return await policy.resolve(context);
1684
1684
  } catch {
1685
1685
  throw new AiTokensException("AI_TOKENS_INVALID_CONFIG", void 0, {
1686
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
1686
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
1687
1687
  reason: "markup policy failed to resolve a multiplier"
1688
1688
  });
1689
1689
  }
@@ -1694,7 +1694,7 @@ var MarkupResolver = class {
1694
1694
  return resolveMultiplier4dp(raw);
1695
1695
  } catch {
1696
1696
  throw new AiTokensException("AI_TOKENS_INVALID_CONFIG", void 0, {
1697
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
1697
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
1698
1698
  reason: "markup policy returned an invalid multiplier",
1699
1699
  value: raw
1700
1700
  });
@@ -2126,6 +2126,19 @@ function tokenCountsOf(usage) {
2126
2126
  function sumTokenCounts(counts) {
2127
2127
  return TOKEN_FIELDS.reduce((total, field) => total + counts[field], 0);
2128
2128
  }
2129
+ function clampUsageCounts(usage) {
2130
+ const counts = tokenCountsOf(usage);
2131
+ const clamped = zeroTokenCounts();
2132
+ for (const field of TOKEN_FIELDS) {
2133
+ clamped[field] = counts[field] < 0 ? 0 : counts[field];
2134
+ }
2135
+ const result = { ...usage, ...clamped };
2136
+ const cost = usage.providerReportedCostNanoUsd;
2137
+ if (cost !== void 0 && cost < 0n) {
2138
+ return { ...result, providerReportedCostNanoUsd: 0n };
2139
+ }
2140
+ return result;
2141
+ }
2129
2142
  function isNormalizedUsage(usage) {
2130
2143
  if (typeof usage !== "object" || usage === null) return false;
2131
2144
  const candidate = usage;
@@ -2163,7 +2176,7 @@ async function resolveHoldEstimate(pricing, context, estimate, at) {
2163
2176
  operation: estimate.operation,
2164
2177
  serviceTier: tier,
2165
2178
  at,
2166
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { baseModel: undefined } is equivalent to {} because resolveRate reads input.baseModel (undefined either way) — same cache key and same `baseModel !== undefined` resolution branch
2179
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { baseModel: undefined } is equivalent to {} because resolveRate reads input.baseModel (undefined either way) — same cache key and same `baseModel !== undefined` resolution branch
2167
2180
  ...context.baseModel !== void 0 ? { baseModel: context.baseModel } : {}
2168
2181
  });
2169
2182
  const rawEstimateNanoUsd = rate === null ? 0n : computeCostNanoUsd(usage, rate).totalNanoUsd;
@@ -2187,7 +2200,7 @@ async function resolveHoldEstimate(pricing, context, estimate, at) {
2187
2200
  async function resolveTokenEstimate(pricing, context, tokens, serviceTier, at) {
2188
2201
  if (context.preset === void 0) {
2189
2202
  throw new AiTokensException("AI_TOKENS_INVALID_CONFIG", void 0, {
2190
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
2203
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
2191
2204
  reason: "a { tokens } hold estimate requires context.preset to identify the provider"
2192
2205
  });
2193
2206
  }
@@ -2202,17 +2215,17 @@ async function resolveTokenEstimate(pricing, context, tokens, serviceTier, at) {
2202
2215
  return { provider, model, operation: "chat", serviceTier, rawEstimateNanoUsd, estimatedTokens: tokens, tokenCounts };
2203
2216
  }
2204
2217
  function normalizeCaptureUsage(usage, normalizer) {
2205
- if (isNormalizedUsage(usage)) return usage;
2218
+ if (isNormalizedUsage(usage)) return clampUsageCounts(usage);
2206
2219
  if (normalizer !== void 0) {
2207
2220
  try {
2208
- return normalizer(usage);
2221
+ return clampUsageCounts(normalizer(usage));
2209
2222
  } catch (error) {
2210
2223
  if (error instanceof AiTokensException) throw error;
2211
2224
  throw new AiTokensException("AI_TOKENS_USAGE_MALFORMED", void 0, { reason: "the normalizer could not read the usage token fields" });
2212
2225
  }
2213
2226
  }
2214
2227
  throw new AiTokensException("AI_TOKENS_USAGE_MALFORMED", void 0, {
2215
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
2228
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
2216
2229
  reason: "capture requires an already-normalized usage or a preset normalizer"
2217
2230
  });
2218
2231
  }
@@ -2241,7 +2254,7 @@ function buildAppendInput(args) {
2241
2254
  audioOutTokens: normalized.audioOutTokens,
2242
2255
  imageInTokens: normalized.imageInTokens,
2243
2256
  imageOutTokens: normalized.imageOutTokens,
2244
- // Stryker disable next-line ConditionalExpression,EqualityOperator -- CE/EQ: when ratedUnits is empty, spreading { extraUnits: {} } vs omitting the key; downstream consumers check record.extraUnits !== undefined (absent vs present {}); unit tests don't use server tool use, so ratedUnits={} always → no observable difference
2257
+ // Stryker disable next-line ConditionalExpression,EqualityOperator: CE/EQ: when ratedUnits is empty, spreading { extraUnits: {} } vs omitting the key; downstream consumers check record.extraUnits !== undefined (absent vs present {}); unit tests don't use server tool use, so ratedUnits={} always → no observable difference
2245
2258
  ...Object.keys(ratedUnits).length > 0 ? { extraUnits: ratedUnits } : {},
2246
2259
  priceVersionId: rating.priceVersionId,
2247
2260
  rawCostNanoUsd: rating.rawCostNanoUsd,
@@ -2252,7 +2265,7 @@ function buildAppendInput(args) {
2252
2265
  priceMissing: rating.priceMissing,
2253
2266
  status: "posted",
2254
2267
  isSystemCost: context.isSystemCost ?? false,
2255
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { systemCostCategory: undefined } is equivalent to {} because JS property access treats absent and undefined the same
2268
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { systemCostCategory: undefined } is equivalent to {} because JS property access treats absent and undefined the same
2256
2269
  ...context.systemCostCategory !== void 0 ? { systemCostCategory: context.systemCostCategory } : {},
2257
2270
  enforced,
2258
2271
  occurredAt
@@ -2262,19 +2275,19 @@ function baseColumns(usage, context, serviceTier) {
2262
2275
  return {
2263
2276
  tenantId: context.tenantId,
2264
2277
  scope: context.scope,
2265
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { beneficiary: undefined } is equivalent to {} for property access and truthiness-based filters
2278
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { beneficiary: undefined } is equivalent to {} for property access and truthiness-based filters
2266
2279
  ...context.beneficiary !== void 0 ? { beneficiary: context.beneficiary } : {},
2267
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { requestedBy: undefined } is equivalent to {}
2280
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { requestedBy: undefined } is equivalent to {}
2268
2281
  ...context.requestedBy !== void 0 ? { requestedBy: context.requestedBy } : {},
2269
2282
  provider: usage.provider,
2270
2283
  model: usage.model,
2271
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { requestedModel: undefined } is equivalent to {} for downstream consumers
2284
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { requestedModel: undefined } is equivalent to {} for downstream consumers
2272
2285
  ...context.baseModel !== void 0 ? { requestedModel: context.baseModel } : {},
2273
2286
  operation: usage.operation,
2274
2287
  serviceTier,
2275
2288
  feature: context.feature,
2276
2289
  tags: context.tags ?? [],
2277
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { correlationId: undefined } is equivalent to {} because it's only read via record.correlationId
2290
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { correlationId: undefined } is equivalent to {} because it's only read via record.correlationId
2278
2291
  ...context.correlationId !== void 0 ? { correlationId: context.correlationId } : {}
2279
2292
  };
2280
2293
  }
@@ -2336,7 +2349,7 @@ exports.MeteringService = class MeteringService {
2336
2349
  const enforcing = context.enforce === true;
2337
2350
  if (enforcing && this.wallets === void 0 && this.budgets === void 0) {
2338
2351
  throw new AiTokensException("AI_TOKENS_INVALID_CONFIG", void 0, {
2339
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
2352
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
2340
2353
  reason: "enforce requires the wallets or budgets feature to be enabled"
2341
2354
  });
2342
2355
  }
@@ -2391,7 +2404,7 @@ exports.MeteringService = class MeteringService {
2391
2404
  model: input.model,
2392
2405
  operation: input.operation,
2393
2406
  serviceTier,
2394
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { feature: undefined } is equivalent to {} because markup.resolve treats absent and undefined feature the same
2407
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { feature: undefined } is equivalent to {} because markup.resolve treats absent and undefined feature the same
2395
2408
  ...input.feature !== void 0 ? { feature: input.feature } : {}
2396
2409
  });
2397
2410
  return { rawCostNanoUsd, billedCostNanoUsd: markup.apply(rawCostNanoUsd) };
@@ -2593,7 +2606,7 @@ exports.MeteringService = class MeteringService {
2593
2606
  priceMissing: false,
2594
2607
  status: "pending",
2595
2608
  isSystemCost,
2596
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { systemCostCategory: undefined } is equivalent to {}
2609
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { systemCostCategory: undefined } is equivalent to {}
2597
2610
  ...context.systemCostCategory !== void 0 ? { systemCostCategory: context.systemCostCategory } : {},
2598
2611
  enforced: !isSystemCost,
2599
2612
  occurredAt
@@ -2659,9 +2672,9 @@ exports.MeteringService = class MeteringService {
2659
2672
  if (usage instanceof StreamUsageCollector) {
2660
2673
  const finalized = await usage.finalize();
2661
2674
  if (usage.usedFallback && finalized.inputTokens === 0 && hold.estimatedTokens > 0) {
2662
- return { ...finalized, inputTokens: hold.estimatedTokens };
2675
+ return normalizeCaptureUsage({ ...finalized, inputTokens: hold.estimatedTokens });
2663
2676
  }
2664
- return finalized;
2677
+ return normalizeCaptureUsage(finalized);
2665
2678
  }
2666
2679
  return normalizeCaptureUsage(usage, preset2?.normalizer);
2667
2680
  }
@@ -2691,7 +2704,7 @@ exports.MeteringService = class MeteringService {
2691
2704
  if (normalizer !== void 0) return this.normalize(normalizer, input.usage);
2692
2705
  if (isNormalizedUsage(input.usage)) return input.usage;
2693
2706
  throw new AiTokensException("AI_TOKENS_UNKNOWN_PROVIDER", void 0, {
2694
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
2707
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
2695
2708
  reason: "raw usage requires a preset or normalizer, or an already-normalized usage"
2696
2709
  });
2697
2710
  }
@@ -2794,7 +2807,7 @@ exports.WalletService = class WalletService {
2794
2807
  amountNanoUsd: input.amountNanoUsd,
2795
2808
  priority: input.priority ?? 0,
2796
2809
  effectiveAt: input.effectiveAt ?? /* @__PURE__ */ new Date(),
2797
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { expiresAt: undefined } is equivalent to {} because InMemoryWalletStore and consumers check expiresAt !== undefined
2810
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { expiresAt: undefined } is equivalent to {} because InMemoryWalletStore and consumers check expiresAt !== undefined
2798
2811
  ...input.expiresAt !== void 0 ? { expiresAt: input.expiresAt } : {},
2799
2812
  idempotencyKey: input.idempotencyKey,
2800
2813
  reason: input.reason
@@ -2803,7 +2816,7 @@ exports.WalletService = class WalletService {
2803
2816
  walletId: entry.walletId,
2804
2817
  entryId: entry.id,
2805
2818
  amountNanoUsd: entry.amountNanoUsd,
2806
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { expiresAt: undefined } is equivalent to {} because event consumers check expiresAt !== undefined
2819
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { expiresAt: undefined } is equivalent to {} because event consumers check expiresAt !== undefined
2807
2820
  ...entry.expiresAt !== void 0 ? { expiresAt: entry.expiresAt } : {}
2808
2821
  };
2809
2822
  await this.events.granted(ref, granted);
@@ -2834,16 +2847,16 @@ exports.WalletService = class WalletService {
2834
2847
  throw this.invalid("a debit without a usageRecordId must carry a reason");
2835
2848
  }
2836
2849
  const entry = {
2837
- // Stryker disable next-line StringLiteral -- placeholder walletId; the store's insert always assigns the real wallet id and never reads this sentinel, so any string here is equivalent
2850
+ // Stryker disable next-line StringLiteral: placeholder walletId; the store's insert always assigns the real wallet id and never reads this sentinel, so any string here is equivalent
2838
2851
  walletId: "",
2839
2852
  type: "debit",
2840
2853
  amountNanoUsd: -input.amountNanoUsd,
2841
2854
  priority: 0,
2842
2855
  effectiveAt: /* @__PURE__ */ new Date(),
2843
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2856
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2844
2857
  ...input.usageRecordId !== void 0 ? { usageRecordId: input.usageRecordId } : {},
2845
2858
  idempotencyKey: input.idempotencyKey,
2846
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { reason: undefined } is equivalent to {} because store and consumers check reason !== undefined
2859
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { reason: undefined } is equivalent to {} because store and consumers check reason !== undefined
2847
2860
  ...input.reason !== void 0 ? { reason: input.reason } : {}
2848
2861
  };
2849
2862
  const debited = await this.conditionalDebit(ref, entry, input.amountNanoUsd);
@@ -2870,7 +2883,7 @@ exports.WalletService = class WalletService {
2870
2883
  amountNanoUsd: input.amountNanoUsd,
2871
2884
  priority: 0,
2872
2885
  effectiveAt: /* @__PURE__ */ new Date(),
2873
- // Stryker disable next-line ConditionalExpression,ObjectLiteral -- CE true / OL {}: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2886
+ // Stryker disable next-line ConditionalExpression,ObjectLiteral: CE true / OL {}: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2874
2887
  ...input.usageRecordId !== void 0 ? { usageRecordId: input.usageRecordId } : {},
2875
2888
  idempotencyKey: input.idempotencyKey,
2876
2889
  reason: input.reason
@@ -2927,8 +2940,8 @@ exports.WalletService = class WalletService {
2927
2940
  amountNanoUsd: input.amountNanoUsd,
2928
2941
  priority: 0,
2929
2942
  effectiveAt: /* @__PURE__ */ new Date(),
2930
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2931
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2943
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2944
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2932
2945
  ...input.usageRecordId !== void 0 ? { usageRecordId: input.usageRecordId } : {},
2933
2946
  idempotencyKey: input.idempotencyKey,
2934
2947
  reason: input.reason
@@ -3560,11 +3573,11 @@ function featureMatches3(features, feature) {
3560
3573
  }
3561
3574
  function limitsOf(budget) {
3562
3575
  return {
3563
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { nanoUsd: undefined } is equivalent to {} because all consumers check limits.nanoUsd !== undefined
3576
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { nanoUsd: undefined } is equivalent to {} because all consumers check limits.nanoUsd !== undefined
3564
3577
  ...budget.limitNanoUsd !== void 0 ? { nanoUsd: budget.limitNanoUsd } : {},
3565
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { tokens: undefined } is equivalent to {} because all consumers check limits.tokens !== undefined
3578
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { tokens: undefined } is equivalent to {} because all consumers check limits.tokens !== undefined
3566
3579
  ...budget.limitTokens !== void 0 ? { tokens: budget.limitTokens } : {},
3567
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { count: undefined } is equivalent to {} because all consumers check limits.count !== undefined
3580
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { count: undefined } is equivalent to {} because all consumers check limits.count !== undefined
3568
3581
  ...budget.limitCount !== void 0 ? { count: budget.limitCount } : {}
3569
3582
  };
3570
3583
  }
@@ -3604,11 +3617,11 @@ function ratio(spent, limit) {
3604
3617
  }
3605
3618
  function limitSnapshot(limits) {
3606
3619
  return {
3607
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { nanoUsd: undefined } is equivalent to {} for event consumers who check field !== undefined
3620
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { nanoUsd: undefined } is equivalent to {} for event consumers who check field !== undefined
3608
3621
  ...limits.nanoUsd !== void 0 ? { nanoUsd: limits.nanoUsd } : {},
3609
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { tokens: undefined } is equivalent to {} for event consumers who check field !== undefined
3622
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { tokens: undefined } is equivalent to {} for event consumers who check field !== undefined
3610
3623
  ...limits.tokens !== void 0 ? { tokens: limits.tokens } : {},
3611
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { count: undefined } is equivalent to {} for event consumers who check field !== undefined
3624
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { count: undefined } is equivalent to {} for event consumers who check field !== undefined
3612
3625
  ...limits.count !== void 0 ? { count: limits.count } : {}
3613
3626
  };
3614
3627
  }
@@ -3617,11 +3630,11 @@ function spendSnapshot(spend) {
3617
3630
  }
3618
3631
  function remainingSnapshot(limits, spend) {
3619
3632
  return {
3620
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { nanoUsd: undefined } is equivalent to {} for event consumers who check field !== undefined
3633
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { nanoUsd: undefined } is equivalent to {} for event consumers who check field !== undefined
3621
3634
  ...limits.nanoUsd !== void 0 ? { nanoUsd: max0(limits.nanoUsd - spend.spentNanoUsd) } : {},
3622
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { tokens: undefined } is equivalent to {} for event consumers who check field !== undefined
3635
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { tokens: undefined } is equivalent to {} for event consumers who check field !== undefined
3623
3636
  ...limits.tokens !== void 0 ? { tokens: Math.max(0, limits.tokens - spend.spentTokens) } : {},
3624
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { count: undefined } is equivalent to {} for event consumers who check field !== undefined
3637
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { count: undefined } is equivalent to {} for event consumers who check field !== undefined
3625
3638
  ...limits.count !== void 0 ? { count: Math.max(0, limits.count - spend.spentCount) } : {}
3626
3639
  };
3627
3640
  }
@@ -3629,7 +3642,7 @@ function buildStatus(budget, windowStart, resetsAt, spend) {
3629
3642
  const limits = limitsOf(budget);
3630
3643
  return {
3631
3644
  budgetId: budget.id,
3632
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { features: undefined } is equivalent to {} because status consumers check features !== undefined
3645
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { features: undefined } is equivalent to {} because status consumers check features !== undefined
3633
3646
  ...budget.features !== void 0 ? { features: budget.features } : {},
3634
3647
  window: budget.window,
3635
3648
  windowStart,
@@ -3796,29 +3809,29 @@ exports.UsageReportService = __decorateClass([
3796
3809
  function toLedgerFilter(filter, limit) {
3797
3810
  return {
3798
3811
  tenantId: filter.tenantId,
3799
- // Stryker disable next-line ConditionalExpression -- spreading { scope: undefined } is equivalent to {} because InMemoryLedgerStore uses truthiness; the 'false' variant is covered by the scope isolation test
3812
+ // Stryker disable next-line ConditionalExpression: spreading { scope: undefined } is equivalent to {} because InMemoryLedgerStore uses truthiness; the 'false' variant is covered by the scope isolation test
3800
3813
  ...filter.scope !== void 0 ? { scope: filter.scope } : {},
3801
- // Stryker disable next-line ConditionalExpression -- spreading { beneficiary: undefined } is equivalent to {} for the same reason
3814
+ // Stryker disable next-line ConditionalExpression: spreading { beneficiary: undefined } is equivalent to {} for the same reason
3802
3815
  ...filter.beneficiary !== void 0 ? { beneficiary: filter.beneficiary } : {},
3803
- // Stryker disable next-line ConditionalExpression -- spreading { feature: undefined } does not change the !== undefined check in matches()
3816
+ // Stryker disable next-line ConditionalExpression: spreading { feature: undefined } does not change the !== undefined check in matches()
3804
3817
  ...filter.feature !== void 0 ? { feature: filter.feature } : {},
3805
- // Stryker disable next-line ConditionalExpression -- spreading { features: undefined } does not change the truthiness check in matches()
3818
+ // Stryker disable next-line ConditionalExpression: spreading { features: undefined } does not change the truthiness check in matches()
3806
3819
  ...filter.features !== void 0 ? { features: filter.features } : {},
3807
- // Stryker disable next-line ConditionalExpression -- spreading { provider: undefined } does not change the !== undefined check in matches()
3820
+ // Stryker disable next-line ConditionalExpression: spreading { provider: undefined } does not change the !== undefined check in matches()
3808
3821
  ...filter.provider !== void 0 ? { provider: filter.provider } : {},
3809
- // Stryker disable next-line ConditionalExpression -- spreading { model: undefined } does not change the !== undefined check in matches()
3822
+ // Stryker disable next-line ConditionalExpression: spreading { model: undefined } does not change the !== undefined check in matches()
3810
3823
  ...filter.model !== void 0 ? { model: filter.model } : {},
3811
- // Stryker disable next-line ConditionalExpression -- spreading { operation: undefined } does not change the !== undefined check in matches()
3824
+ // Stryker disable next-line ConditionalExpression: spreading { operation: undefined } does not change the !== undefined check in matches()
3812
3825
  ...filter.operation !== void 0 ? { operation: filter.operation } : {},
3813
- // Stryker disable next-line ConditionalExpression -- spreading { serviceTier: undefined } does not change the !== undefined check in matches()
3826
+ // Stryker disable next-line ConditionalExpression: spreading { serviceTier: undefined } does not change the !== undefined check in matches()
3814
3827
  ...filter.serviceTier !== void 0 ? { serviceTier: filter.serviceTier } : {},
3815
- // Stryker disable next-line ConditionalExpression -- spreading { tags: undefined } does not change the truthiness check in matches()
3828
+ // Stryker disable next-line ConditionalExpression: spreading { tags: undefined } does not change the truthiness check in matches()
3816
3829
  ...filter.tags !== void 0 ? { tags: filter.tags } : {},
3817
- // Stryker disable next-line ConditionalExpression -- spreading { isSystemCost: undefined } does not change the !== undefined check in matches()
3830
+ // Stryker disable next-line ConditionalExpression: spreading { isSystemCost: undefined } does not change the !== undefined check in matches()
3818
3831
  ...filter.isSystemCost !== void 0 ? { isSystemCost: filter.isSystemCost } : {},
3819
- // Stryker disable next-line ConditionalExpression -- spreading { systemCostCategory: undefined } does not change the !== undefined check in matches()
3832
+ // Stryker disable next-line ConditionalExpression: spreading { systemCostCategory: undefined } does not change the !== undefined check in matches()
3820
3833
  ...filter.systemCostCategory !== void 0 ? { systemCostCategory: filter.systemCostCategory } : {},
3821
- // Stryker disable next-line ConditionalExpression -- spreading { enforcedOnly: undefined } does not change the === true check in matches()
3834
+ // Stryker disable next-line ConditionalExpression: spreading { enforcedOnly: undefined } does not change the === true check in matches()
3822
3835
  ...filter.enforcedOnly !== void 0 ? { enforcedOnly: filter.enforcedOnly } : {},
3823
3836
  status: filter.status ?? [...BALANCE_STATUSES2],
3824
3837
  from: filter.from,
@@ -4004,9 +4017,9 @@ exports.MeteringInterceptor = class MeteringInterceptor {
4004
4017
  const context = await this.resolveContext(executionContext, config, enrichment);
4005
4018
  return this.metering.record({
4006
4019
  usage,
4007
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { preset: undefined } is equivalent to {} because MeteringService checks preset !== undefined
4020
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { preset: undefined } is equivalent to {} because MeteringService checks preset !== undefined
4008
4021
  ...config.preset !== void 0 ? { preset: config.preset } : {},
4009
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { tags: undefined } is equivalent to {} because MeteringService checks tags !== undefined
4022
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { tags: undefined } is equivalent to {} because MeteringService checks tags !== undefined
4010
4023
  context: { ...context, enforce: true, isSystemCost: config.isSystemCost ?? false, ...config.tags !== void 0 ? { tags: config.tags } : {} }
4011
4024
  });
4012
4025
  }
@@ -4334,24 +4347,24 @@ function preset(provider, normalizer, ratingMode) {
4334
4347
  return { provider, normalizer, ratingMode };
4335
4348
  }
4336
4349
  var providerPresets = {
4337
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests that exercise the full preset→MeteringService→UsageRecord path
4350
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests that exercise the full preset→MeteringService→UsageRecord path
4338
4351
  openaiChat: preset("openai", normalizeOpenAiChatUsage, "rate-table"),
4339
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests
4352
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests
4340
4353
  openaiResponses: preset("openai", normalizeOpenAiResponsesUsage, "rate-table"),
4341
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests
4354
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests
4342
4355
  azureOpenai: preset("azure-openai", normalizeOpenAiChatUsage, "rate-table"),
4343
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests
4356
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests
4344
4357
  anthropic: preset("anthropic", normalizeAnthropicUsage, "rate-table"),
4345
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests
4358
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests
4346
4359
  gemini: preset("gemini", normalizeGeminiUsage, "rate-table"),
4347
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests
4360
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests
4348
4361
  vertex: preset("vertex", normalizeGeminiUsage, "rate-table"),
4349
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests
4362
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests
4350
4363
  bedrock: preset("bedrock", normalizeBedrockConverseUsage, "rate-table"),
4351
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests
4364
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests
4352
4365
  mistral: preset("mistral", normalizeMistralUsage, "rate-table"),
4353
4366
  vercelAiSdk: preset("openai", normalizeVercelAiSdkUsage, "rate-table"),
4354
- // Stryker disable next-line StringLiteral -- provider ID and rating mode; only observable via integration tests
4367
+ // Stryker disable next-line StringLiteral: provider ID and rating mode; only observable via integration tests
4355
4368
  openrouter: preset("openrouter", normalizeOpenRouterUsage, "provider-reported"),
4356
4369
  /** A custom OpenAI-compatible provider (DeepSeek, xAI, Groq, …). */
4357
4370
  openaiCompatible: (id) => preset(id, normalizeOpenAiCompatibleUsage, "rate-table")
@@ -2553,7 +2553,14 @@ interface RecordInput {
2553
2553
  normalizer?: UsageNormalizer;
2554
2554
  /** The per-call metering context (trusted input from the host's auth layer). */
2555
2555
  context: MeteringContext;
2556
- /** When the call happened; defaults to now (backfills pass the original time). */
2556
+ /**
2557
+ * When the call happened; defaults to now. Backfills deliberately pass the original time so a
2558
+ * usage record imported late still lands in the price version and budget window in effect when
2559
+ * it actually occurred. Because that timestamp selects both, it is honoured as given — it is
2560
+ * trusted host input, like the rest of {@link MeteringContext}. A host that forwards a value an
2561
+ * untrusted end user controls must validate it first, since a back-dated `occurredAt` would pick
2562
+ * a historical price and an earlier budget window.
2563
+ */
2557
2564
  occurredAt?: Date;
2558
2565
  /**
2559
2566
  * The ONLY path prompt/completion text enters the library (spec §14.2): masked +
@@ -2553,7 +2553,14 @@ interface RecordInput {
2553
2553
  normalizer?: UsageNormalizer;
2554
2554
  /** The per-call metering context (trusted input from the host's auth layer). */
2555
2555
  context: MeteringContext;
2556
- /** When the call happened; defaults to now (backfills pass the original time). */
2556
+ /**
2557
+ * When the call happened; defaults to now. Backfills deliberately pass the original time so a
2558
+ * usage record imported late still lands in the price version and budget window in effect when
2559
+ * it actually occurred. Because that timestamp selects both, it is honoured as given — it is
2560
+ * trusted host input, like the rest of {@link MeteringContext}. A host that forwards a value an
2561
+ * untrusted end user controls must validate it first, since a back-dated `occurredAt` would pick
2562
+ * a historical price and an earlier budget window.
2563
+ */
2557
2564
  occurredAt?: Date;
2558
2565
  /**
2559
2566
  * The ONLY path prompt/completion text enters the library (spec §14.2): masked +
@@ -360,9 +360,9 @@ function normalizeMistralUsage(raw) {
360
360
  provider: "mistral",
361
361
  model: str(response.model) ?? "",
362
362
  operation: "chat",
363
- // Stryker disable next-line StringLiteral -- provider and field names in error messages are internal diagnostics
363
+ // Stryker disable next-line StringLiteral: provider and field names in error messages are internal diagnostics
364
364
  inputTokens: requireNum(usage.prompt_tokens, "mistral", "usage.prompt_tokens"),
365
- // Stryker disable next-line StringLiteral -- provider and field names in error messages are internal diagnostics
365
+ // Stryker disable next-line StringLiteral: provider and field names in error messages are internal diagnostics
366
366
  outputTokens: requireNum(usage.completion_tokens, "mistral", "usage.completion_tokens"),
367
367
  raw: usage
368
368
  });
@@ -806,7 +806,7 @@ function validatePricing(options) {
806
806
  if (cacheTtlMs === void 0) return;
807
807
  if (!Number.isInteger(cacheTtlMs) || cacheTtlMs <= 0) {
808
808
  throw invalidConfig(
809
- // Stryker disable next-line StringLiteral -- error reason template is internal diagnostics
809
+ // Stryker disable next-line StringLiteral: error reason template is internal diagnostics
810
810
  `pricing.cacheTtlMs must be a positive integer number of milliseconds, received ${String(cacheTtlMs)}`
811
811
  );
812
812
  }
@@ -1126,7 +1126,7 @@ var BudgetGuard = class {
1126
1126
  this.reflector = reflector;
1127
1127
  if (options.scopeResolver === void 0) {
1128
1128
  throw new AiTokensException("AI_TOKENS_INVALID_CONFIG", void 0, {
1129
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
1129
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
1130
1130
  reason: "BudgetGuard requires options.scopeResolver to resolve the caller scope"
1131
1131
  });
1132
1132
  }
@@ -1357,7 +1357,7 @@ function computePayloadHash(record) {
1357
1357
  // src/server/utils/hash-chain.ts
1358
1358
  function chainHash(prevHash, record) {
1359
1359
  return deriveIdempotencyKey({
1360
- // Stryker disable next-line StringLiteral -- empty-string fallback for null prevHash; tests only verify hash determinism and inequality, not the specific hash value for a genesis block
1360
+ // Stryker disable next-line StringLiteral: empty-string fallback for null prevHash; tests only verify hash determinism and inequality, not the specific hash value for a genesis block
1361
1361
  prevHash: prevHash ?? "",
1362
1362
  id: record.id,
1363
1363
  content: computePayloadHash(record)
@@ -1558,7 +1558,7 @@ var LedgerService = class {
1558
1558
  const original = await this.store.findById(recordId);
1559
1559
  if (original?.status !== "posted") {
1560
1560
  throw new AiTokensException("AI_TOKENS_IDEMPOTENCY_CONFLICT", void 0, {
1561
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
1561
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
1562
1562
  reason: original === null ? "record not found" : `cannot reverse a ${original.status} record`,
1563
1563
  recordId,
1564
1564
  requestedReason: reason
@@ -1579,7 +1579,7 @@ var LedgerService = class {
1579
1579
  assertLegalTransition(from2, to, patch) {
1580
1580
  if (!isLegalLedgerTransition(from2, to)) {
1581
1581
  throw new AiTokensException("AI_TOKENS_IDEMPOTENCY_CONFLICT", void 0, {
1582
- // Stryker disable next-line StringLiteral -- error reason template is internal diagnostics
1582
+ // Stryker disable next-line StringLiteral: error reason template is internal diagnostics
1583
1583
  reason: `illegal transition ${from2} \u2192 ${to}`
1584
1584
  });
1585
1585
  }
@@ -1587,7 +1587,7 @@ var LedgerService = class {
1587
1587
  for (const key of Object.keys(patch)) {
1588
1588
  if (!isLegalTransitionPatchKey(from2, to, key)) {
1589
1589
  throw new AiTokensException("AI_TOKENS_IDEMPOTENCY_CONFLICT", void 0, {
1590
- // Stryker disable next-line StringLiteral -- error reason template is internal diagnostics
1590
+ // Stryker disable next-line StringLiteral: error reason template is internal diagnostics
1591
1591
  reason: `${from2} \u2192 ${to} may not patch "${key}"`
1592
1592
  });
1593
1593
  }
@@ -1606,13 +1606,13 @@ function buildCompensatingRecord(original) {
1606
1606
  return {
1607
1607
  tenantId: original.tenantId,
1608
1608
  scope: original.scope,
1609
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { beneficiary: undefined } is equivalent to {} because downstream consumers check !== undefined
1609
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { beneficiary: undefined } is equivalent to {} because downstream consumers check !== undefined
1610
1610
  ...original.beneficiary !== void 0 ? { beneficiary: original.beneficiary } : {},
1611
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { requestedBy: undefined } is equivalent to {}
1611
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { requestedBy: undefined } is equivalent to {}
1612
1612
  ...original.requestedBy !== void 0 ? { requestedBy: original.requestedBy } : {},
1613
1613
  provider: original.provider,
1614
1614
  model: original.model,
1615
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { requestedModel: undefined } is equivalent to {}
1615
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { requestedModel: undefined } is equivalent to {}
1616
1616
  ...original.requestedModel !== void 0 ? { requestedModel: original.requestedModel } : {},
1617
1617
  operation: original.operation,
1618
1618
  serviceTier: original.serviceTier,
@@ -1628,7 +1628,7 @@ function buildCompensatingRecord(original) {
1628
1628
  audioOutTokens: -original.audioOutTokens,
1629
1629
  imageInTokens: -original.imageInTokens,
1630
1630
  imageOutTokens: -original.imageOutTokens,
1631
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { extraUnits: undefined } is equivalent to {} because consumers check !== undefined
1631
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { extraUnits: undefined } is equivalent to {} because consumers check !== undefined
1632
1632
  ...original.extraUnits !== void 0 ? { extraUnits: negateUnits(original.extraUnits) } : {},
1633
1633
  priceVersionId: original.priceVersionId,
1634
1634
  rawCostNanoUsd: -original.rawCostNanoUsd,
@@ -1639,12 +1639,12 @@ function buildCompensatingRecord(original) {
1639
1639
  priceMissing: original.priceMissing,
1640
1640
  status: "posted",
1641
1641
  reversesRecordId: original.id,
1642
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { correlationId: undefined } is equivalent to {}
1642
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { correlationId: undefined } is equivalent to {}
1643
1643
  ...original.correlationId !== void 0 ? { correlationId: original.correlationId } : {},
1644
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { requestId: undefined } is equivalent to {}
1644
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { requestId: undefined } is equivalent to {}
1645
1645
  ...original.requestId !== void 0 ? { requestId: original.requestId } : {},
1646
1646
  isSystemCost: original.isSystemCost,
1647
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { systemCostCategory: undefined } is equivalent to {}
1647
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { systemCostCategory: undefined } is equivalent to {}
1648
1648
  ...original.systemCostCategory !== void 0 ? { systemCostCategory: original.systemCostCategory } : {},
1649
1649
  enforced: original.enforced,
1650
1650
  occurredAt: original.occurredAt
@@ -1681,7 +1681,7 @@ var MarkupResolver = class {
1681
1681
  return await policy.resolve(context);
1682
1682
  } catch {
1683
1683
  throw new AiTokensException("AI_TOKENS_INVALID_CONFIG", void 0, {
1684
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
1684
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
1685
1685
  reason: "markup policy failed to resolve a multiplier"
1686
1686
  });
1687
1687
  }
@@ -1692,7 +1692,7 @@ var MarkupResolver = class {
1692
1692
  return resolveMultiplier4dp(raw);
1693
1693
  } catch {
1694
1694
  throw new AiTokensException("AI_TOKENS_INVALID_CONFIG", void 0, {
1695
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
1695
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
1696
1696
  reason: "markup policy returned an invalid multiplier",
1697
1697
  value: raw
1698
1698
  });
@@ -2124,6 +2124,19 @@ function tokenCountsOf(usage) {
2124
2124
  function sumTokenCounts(counts) {
2125
2125
  return TOKEN_FIELDS.reduce((total, field) => total + counts[field], 0);
2126
2126
  }
2127
+ function clampUsageCounts(usage) {
2128
+ const counts = tokenCountsOf(usage);
2129
+ const clamped = zeroTokenCounts();
2130
+ for (const field of TOKEN_FIELDS) {
2131
+ clamped[field] = counts[field] < 0 ? 0 : counts[field];
2132
+ }
2133
+ const result = { ...usage, ...clamped };
2134
+ const cost = usage.providerReportedCostNanoUsd;
2135
+ if (cost !== void 0 && cost < 0n) {
2136
+ return { ...result, providerReportedCostNanoUsd: 0n };
2137
+ }
2138
+ return result;
2139
+ }
2127
2140
  function isNormalizedUsage(usage) {
2128
2141
  if (typeof usage !== "object" || usage === null) return false;
2129
2142
  const candidate = usage;
@@ -2161,7 +2174,7 @@ async function resolveHoldEstimate(pricing, context, estimate, at) {
2161
2174
  operation: estimate.operation,
2162
2175
  serviceTier: tier,
2163
2176
  at,
2164
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { baseModel: undefined } is equivalent to {} because resolveRate reads input.baseModel (undefined either way) — same cache key and same `baseModel !== undefined` resolution branch
2177
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { baseModel: undefined } is equivalent to {} because resolveRate reads input.baseModel (undefined either way) — same cache key and same `baseModel !== undefined` resolution branch
2165
2178
  ...context.baseModel !== void 0 ? { baseModel: context.baseModel } : {}
2166
2179
  });
2167
2180
  const rawEstimateNanoUsd = rate === null ? 0n : computeCostNanoUsd(usage, rate).totalNanoUsd;
@@ -2185,7 +2198,7 @@ async function resolveHoldEstimate(pricing, context, estimate, at) {
2185
2198
  async function resolveTokenEstimate(pricing, context, tokens, serviceTier, at) {
2186
2199
  if (context.preset === void 0) {
2187
2200
  throw new AiTokensException("AI_TOKENS_INVALID_CONFIG", void 0, {
2188
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
2201
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
2189
2202
  reason: "a { tokens } hold estimate requires context.preset to identify the provider"
2190
2203
  });
2191
2204
  }
@@ -2200,17 +2213,17 @@ async function resolveTokenEstimate(pricing, context, tokens, serviceTier, at) {
2200
2213
  return { provider, model, operation: "chat", serviceTier, rawEstimateNanoUsd, estimatedTokens: tokens, tokenCounts };
2201
2214
  }
2202
2215
  function normalizeCaptureUsage(usage, normalizer) {
2203
- if (isNormalizedUsage(usage)) return usage;
2216
+ if (isNormalizedUsage(usage)) return clampUsageCounts(usage);
2204
2217
  if (normalizer !== void 0) {
2205
2218
  try {
2206
- return normalizer(usage);
2219
+ return clampUsageCounts(normalizer(usage));
2207
2220
  } catch (error) {
2208
2221
  if (error instanceof AiTokensException) throw error;
2209
2222
  throw new AiTokensException("AI_TOKENS_USAGE_MALFORMED", void 0, { reason: "the normalizer could not read the usage token fields" });
2210
2223
  }
2211
2224
  }
2212
2225
  throw new AiTokensException("AI_TOKENS_USAGE_MALFORMED", void 0, {
2213
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
2226
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
2214
2227
  reason: "capture requires an already-normalized usage or a preset normalizer"
2215
2228
  });
2216
2229
  }
@@ -2239,7 +2252,7 @@ function buildAppendInput(args) {
2239
2252
  audioOutTokens: normalized.audioOutTokens,
2240
2253
  imageInTokens: normalized.imageInTokens,
2241
2254
  imageOutTokens: normalized.imageOutTokens,
2242
- // Stryker disable next-line ConditionalExpression,EqualityOperator -- CE/EQ: when ratedUnits is empty, spreading { extraUnits: {} } vs omitting the key; downstream consumers check record.extraUnits !== undefined (absent vs present {}); unit tests don't use server tool use, so ratedUnits={} always → no observable difference
2255
+ // Stryker disable next-line ConditionalExpression,EqualityOperator: CE/EQ: when ratedUnits is empty, spreading { extraUnits: {} } vs omitting the key; downstream consumers check record.extraUnits !== undefined (absent vs present {}); unit tests don't use server tool use, so ratedUnits={} always → no observable difference
2243
2256
  ...Object.keys(ratedUnits).length > 0 ? { extraUnits: ratedUnits } : {},
2244
2257
  priceVersionId: rating.priceVersionId,
2245
2258
  rawCostNanoUsd: rating.rawCostNanoUsd,
@@ -2250,7 +2263,7 @@ function buildAppendInput(args) {
2250
2263
  priceMissing: rating.priceMissing,
2251
2264
  status: "posted",
2252
2265
  isSystemCost: context.isSystemCost ?? false,
2253
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { systemCostCategory: undefined } is equivalent to {} because JS property access treats absent and undefined the same
2266
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { systemCostCategory: undefined } is equivalent to {} because JS property access treats absent and undefined the same
2254
2267
  ...context.systemCostCategory !== void 0 ? { systemCostCategory: context.systemCostCategory } : {},
2255
2268
  enforced,
2256
2269
  occurredAt
@@ -2260,19 +2273,19 @@ function baseColumns(usage, context, serviceTier) {
2260
2273
  return {
2261
2274
  tenantId: context.tenantId,
2262
2275
  scope: context.scope,
2263
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { beneficiary: undefined } is equivalent to {} for property access and truthiness-based filters
2276
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { beneficiary: undefined } is equivalent to {} for property access and truthiness-based filters
2264
2277
  ...context.beneficiary !== void 0 ? { beneficiary: context.beneficiary } : {},
2265
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { requestedBy: undefined } is equivalent to {}
2278
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { requestedBy: undefined } is equivalent to {}
2266
2279
  ...context.requestedBy !== void 0 ? { requestedBy: context.requestedBy } : {},
2267
2280
  provider: usage.provider,
2268
2281
  model: usage.model,
2269
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { requestedModel: undefined } is equivalent to {} for downstream consumers
2282
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { requestedModel: undefined } is equivalent to {} for downstream consumers
2270
2283
  ...context.baseModel !== void 0 ? { requestedModel: context.baseModel } : {},
2271
2284
  operation: usage.operation,
2272
2285
  serviceTier,
2273
2286
  feature: context.feature,
2274
2287
  tags: context.tags ?? [],
2275
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { correlationId: undefined } is equivalent to {} because it's only read via record.correlationId
2288
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { correlationId: undefined } is equivalent to {} because it's only read via record.correlationId
2276
2289
  ...context.correlationId !== void 0 ? { correlationId: context.correlationId } : {}
2277
2290
  };
2278
2291
  }
@@ -2334,7 +2347,7 @@ var MeteringService = class {
2334
2347
  const enforcing = context.enforce === true;
2335
2348
  if (enforcing && this.wallets === void 0 && this.budgets === void 0) {
2336
2349
  throw new AiTokensException("AI_TOKENS_INVALID_CONFIG", void 0, {
2337
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
2350
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
2338
2351
  reason: "enforce requires the wallets or budgets feature to be enabled"
2339
2352
  });
2340
2353
  }
@@ -2389,7 +2402,7 @@ var MeteringService = class {
2389
2402
  model: input.model,
2390
2403
  operation: input.operation,
2391
2404
  serviceTier,
2392
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { feature: undefined } is equivalent to {} because markup.resolve treats absent and undefined feature the same
2405
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { feature: undefined } is equivalent to {} because markup.resolve treats absent and undefined feature the same
2393
2406
  ...input.feature !== void 0 ? { feature: input.feature } : {}
2394
2407
  });
2395
2408
  return { rawCostNanoUsd, billedCostNanoUsd: markup.apply(rawCostNanoUsd) };
@@ -2591,7 +2604,7 @@ var MeteringService = class {
2591
2604
  priceMissing: false,
2592
2605
  status: "pending",
2593
2606
  isSystemCost,
2594
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { systemCostCategory: undefined } is equivalent to {}
2607
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { systemCostCategory: undefined } is equivalent to {}
2595
2608
  ...context.systemCostCategory !== void 0 ? { systemCostCategory: context.systemCostCategory } : {},
2596
2609
  enforced: !isSystemCost,
2597
2610
  occurredAt
@@ -2657,9 +2670,9 @@ var MeteringService = class {
2657
2670
  if (usage instanceof StreamUsageCollector) {
2658
2671
  const finalized = await usage.finalize();
2659
2672
  if (usage.usedFallback && finalized.inputTokens === 0 && hold.estimatedTokens > 0) {
2660
- return { ...finalized, inputTokens: hold.estimatedTokens };
2673
+ return normalizeCaptureUsage({ ...finalized, inputTokens: hold.estimatedTokens });
2661
2674
  }
2662
- return finalized;
2675
+ return normalizeCaptureUsage(finalized);
2663
2676
  }
2664
2677
  return normalizeCaptureUsage(usage, preset2?.normalizer);
2665
2678
  }
@@ -2689,7 +2702,7 @@ var MeteringService = class {
2689
2702
  if (normalizer !== void 0) return this.normalize(normalizer, input.usage);
2690
2703
  if (isNormalizedUsage(input.usage)) return input.usage;
2691
2704
  throw new AiTokensException("AI_TOKENS_UNKNOWN_PROVIDER", void 0, {
2692
- // Stryker disable next-line StringLiteral -- error reason text is internal diagnostics
2705
+ // Stryker disable next-line StringLiteral: error reason text is internal diagnostics
2693
2706
  reason: "raw usage requires a preset or normalizer, or an already-normalized usage"
2694
2707
  });
2695
2708
  }
@@ -2792,7 +2805,7 @@ var WalletService = class {
2792
2805
  amountNanoUsd: input.amountNanoUsd,
2793
2806
  priority: input.priority ?? 0,
2794
2807
  effectiveAt: input.effectiveAt ?? /* @__PURE__ */ new Date(),
2795
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { expiresAt: undefined } is equivalent to {} because InMemoryWalletStore and consumers check expiresAt !== undefined
2808
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { expiresAt: undefined } is equivalent to {} because InMemoryWalletStore and consumers check expiresAt !== undefined
2796
2809
  ...input.expiresAt !== void 0 ? { expiresAt: input.expiresAt } : {},
2797
2810
  idempotencyKey: input.idempotencyKey,
2798
2811
  reason: input.reason
@@ -2801,7 +2814,7 @@ var WalletService = class {
2801
2814
  walletId: entry.walletId,
2802
2815
  entryId: entry.id,
2803
2816
  amountNanoUsd: entry.amountNanoUsd,
2804
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { expiresAt: undefined } is equivalent to {} because event consumers check expiresAt !== undefined
2817
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { expiresAt: undefined } is equivalent to {} because event consumers check expiresAt !== undefined
2805
2818
  ...entry.expiresAt !== void 0 ? { expiresAt: entry.expiresAt } : {}
2806
2819
  };
2807
2820
  await this.events.granted(ref, granted);
@@ -2832,16 +2845,16 @@ var WalletService = class {
2832
2845
  throw this.invalid("a debit without a usageRecordId must carry a reason");
2833
2846
  }
2834
2847
  const entry = {
2835
- // Stryker disable next-line StringLiteral -- placeholder walletId; the store's insert always assigns the real wallet id and never reads this sentinel, so any string here is equivalent
2848
+ // Stryker disable next-line StringLiteral: placeholder walletId; the store's insert always assigns the real wallet id and never reads this sentinel, so any string here is equivalent
2836
2849
  walletId: "",
2837
2850
  type: "debit",
2838
2851
  amountNanoUsd: -input.amountNanoUsd,
2839
2852
  priority: 0,
2840
2853
  effectiveAt: /* @__PURE__ */ new Date(),
2841
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2854
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2842
2855
  ...input.usageRecordId !== void 0 ? { usageRecordId: input.usageRecordId } : {},
2843
2856
  idempotencyKey: input.idempotencyKey,
2844
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { reason: undefined } is equivalent to {} because store and consumers check reason !== undefined
2857
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { reason: undefined } is equivalent to {} because store and consumers check reason !== undefined
2845
2858
  ...input.reason !== void 0 ? { reason: input.reason } : {}
2846
2859
  };
2847
2860
  const debited = await this.conditionalDebit(ref, entry, input.amountNanoUsd);
@@ -2868,7 +2881,7 @@ var WalletService = class {
2868
2881
  amountNanoUsd: input.amountNanoUsd,
2869
2882
  priority: 0,
2870
2883
  effectiveAt: /* @__PURE__ */ new Date(),
2871
- // Stryker disable next-line ConditionalExpression,ObjectLiteral -- CE true / OL {}: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2884
+ // Stryker disable next-line ConditionalExpression,ObjectLiteral: CE true / OL {}: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2872
2885
  ...input.usageRecordId !== void 0 ? { usageRecordId: input.usageRecordId } : {},
2873
2886
  idempotencyKey: input.idempotencyKey,
2874
2887
  reason: input.reason
@@ -2925,8 +2938,8 @@ var WalletService = class {
2925
2938
  amountNanoUsd: input.amountNanoUsd,
2926
2939
  priority: 0,
2927
2940
  effectiveAt: /* @__PURE__ */ new Date(),
2928
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2929
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2941
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2942
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { usageRecordId: undefined } is equivalent to {} because store and consumers check usageRecordId !== undefined
2930
2943
  ...input.usageRecordId !== void 0 ? { usageRecordId: input.usageRecordId } : {},
2931
2944
  idempotencyKey: input.idempotencyKey,
2932
2945
  reason: input.reason
@@ -3558,11 +3571,11 @@ function featureMatches3(features, feature) {
3558
3571
  }
3559
3572
  function limitsOf(budget) {
3560
3573
  return {
3561
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { nanoUsd: undefined } is equivalent to {} because all consumers check limits.nanoUsd !== undefined
3574
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { nanoUsd: undefined } is equivalent to {} because all consumers check limits.nanoUsd !== undefined
3562
3575
  ...budget.limitNanoUsd !== void 0 ? { nanoUsd: budget.limitNanoUsd } : {},
3563
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { tokens: undefined } is equivalent to {} because all consumers check limits.tokens !== undefined
3576
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { tokens: undefined } is equivalent to {} because all consumers check limits.tokens !== undefined
3564
3577
  ...budget.limitTokens !== void 0 ? { tokens: budget.limitTokens } : {},
3565
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { count: undefined } is equivalent to {} because all consumers check limits.count !== undefined
3578
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { count: undefined } is equivalent to {} because all consumers check limits.count !== undefined
3566
3579
  ...budget.limitCount !== void 0 ? { count: budget.limitCount } : {}
3567
3580
  };
3568
3581
  }
@@ -3602,11 +3615,11 @@ function ratio(spent, limit) {
3602
3615
  }
3603
3616
  function limitSnapshot(limits) {
3604
3617
  return {
3605
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { nanoUsd: undefined } is equivalent to {} for event consumers who check field !== undefined
3618
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { nanoUsd: undefined } is equivalent to {} for event consumers who check field !== undefined
3606
3619
  ...limits.nanoUsd !== void 0 ? { nanoUsd: limits.nanoUsd } : {},
3607
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { tokens: undefined } is equivalent to {} for event consumers who check field !== undefined
3620
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { tokens: undefined } is equivalent to {} for event consumers who check field !== undefined
3608
3621
  ...limits.tokens !== void 0 ? { tokens: limits.tokens } : {},
3609
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { count: undefined } is equivalent to {} for event consumers who check field !== undefined
3622
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { count: undefined } is equivalent to {} for event consumers who check field !== undefined
3610
3623
  ...limits.count !== void 0 ? { count: limits.count } : {}
3611
3624
  };
3612
3625
  }
@@ -3615,11 +3628,11 @@ function spendSnapshot(spend) {
3615
3628
  }
3616
3629
  function remainingSnapshot(limits, spend) {
3617
3630
  return {
3618
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { nanoUsd: undefined } is equivalent to {} for event consumers who check field !== undefined
3631
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { nanoUsd: undefined } is equivalent to {} for event consumers who check field !== undefined
3619
3632
  ...limits.nanoUsd !== void 0 ? { nanoUsd: max0(limits.nanoUsd - spend.spentNanoUsd) } : {},
3620
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { tokens: undefined } is equivalent to {} for event consumers who check field !== undefined
3633
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { tokens: undefined } is equivalent to {} for event consumers who check field !== undefined
3621
3634
  ...limits.tokens !== void 0 ? { tokens: Math.max(0, limits.tokens - spend.spentTokens) } : {},
3622
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { count: undefined } is equivalent to {} for event consumers who check field !== undefined
3635
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { count: undefined } is equivalent to {} for event consumers who check field !== undefined
3623
3636
  ...limits.count !== void 0 ? { count: Math.max(0, limits.count - spend.spentCount) } : {}
3624
3637
  };
3625
3638
  }
@@ -3627,7 +3640,7 @@ function buildStatus(budget, windowStart, resetsAt, spend) {
3627
3640
  const limits = limitsOf(budget);
3628
3641
  return {
3629
3642
  budgetId: budget.id,
3630
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { features: undefined } is equivalent to {} because status consumers check features !== undefined
3643
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { features: undefined } is equivalent to {} because status consumers check features !== undefined
3631
3644
  ...budget.features !== void 0 ? { features: budget.features } : {},
3632
3645
  window: budget.window,
3633
3646
  windowStart,
@@ -3794,29 +3807,29 @@ UsageReportService = __decorateClass([
3794
3807
  function toLedgerFilter(filter, limit) {
3795
3808
  return {
3796
3809
  tenantId: filter.tenantId,
3797
- // Stryker disable next-line ConditionalExpression -- spreading { scope: undefined } is equivalent to {} because InMemoryLedgerStore uses truthiness; the 'false' variant is covered by the scope isolation test
3810
+ // Stryker disable next-line ConditionalExpression: spreading { scope: undefined } is equivalent to {} because InMemoryLedgerStore uses truthiness; the 'false' variant is covered by the scope isolation test
3798
3811
  ...filter.scope !== void 0 ? { scope: filter.scope } : {},
3799
- // Stryker disable next-line ConditionalExpression -- spreading { beneficiary: undefined } is equivalent to {} for the same reason
3812
+ // Stryker disable next-line ConditionalExpression: spreading { beneficiary: undefined } is equivalent to {} for the same reason
3800
3813
  ...filter.beneficiary !== void 0 ? { beneficiary: filter.beneficiary } : {},
3801
- // Stryker disable next-line ConditionalExpression -- spreading { feature: undefined } does not change the !== undefined check in matches()
3814
+ // Stryker disable next-line ConditionalExpression: spreading { feature: undefined } does not change the !== undefined check in matches()
3802
3815
  ...filter.feature !== void 0 ? { feature: filter.feature } : {},
3803
- // Stryker disable next-line ConditionalExpression -- spreading { features: undefined } does not change the truthiness check in matches()
3816
+ // Stryker disable next-line ConditionalExpression: spreading { features: undefined } does not change the truthiness check in matches()
3804
3817
  ...filter.features !== void 0 ? { features: filter.features } : {},
3805
- // Stryker disable next-line ConditionalExpression -- spreading { provider: undefined } does not change the !== undefined check in matches()
3818
+ // Stryker disable next-line ConditionalExpression: spreading { provider: undefined } does not change the !== undefined check in matches()
3806
3819
  ...filter.provider !== void 0 ? { provider: filter.provider } : {},
3807
- // Stryker disable next-line ConditionalExpression -- spreading { model: undefined } does not change the !== undefined check in matches()
3820
+ // Stryker disable next-line ConditionalExpression: spreading { model: undefined } does not change the !== undefined check in matches()
3808
3821
  ...filter.model !== void 0 ? { model: filter.model } : {},
3809
- // Stryker disable next-line ConditionalExpression -- spreading { operation: undefined } does not change the !== undefined check in matches()
3822
+ // Stryker disable next-line ConditionalExpression: spreading { operation: undefined } does not change the !== undefined check in matches()
3810
3823
  ...filter.operation !== void 0 ? { operation: filter.operation } : {},
3811
- // Stryker disable next-line ConditionalExpression -- spreading { serviceTier: undefined } does not change the !== undefined check in matches()
3824
+ // Stryker disable next-line ConditionalExpression: spreading { serviceTier: undefined } does not change the !== undefined check in matches()
3812
3825
  ...filter.serviceTier !== void 0 ? { serviceTier: filter.serviceTier } : {},
3813
- // Stryker disable next-line ConditionalExpression -- spreading { tags: undefined } does not change the truthiness check in matches()
3826
+ // Stryker disable next-line ConditionalExpression: spreading { tags: undefined } does not change the truthiness check in matches()
3814
3827
  ...filter.tags !== void 0 ? { tags: filter.tags } : {},
3815
- // Stryker disable next-line ConditionalExpression -- spreading { isSystemCost: undefined } does not change the !== undefined check in matches()
3828
+ // Stryker disable next-line ConditionalExpression: spreading { isSystemCost: undefined } does not change the !== undefined check in matches()
3816
3829
  ...filter.isSystemCost !== void 0 ? { isSystemCost: filter.isSystemCost } : {},
3817
- // Stryker disable next-line ConditionalExpression -- spreading { systemCostCategory: undefined } does not change the !== undefined check in matches()
3830
+ // Stryker disable next-line ConditionalExpression: spreading { systemCostCategory: undefined } does not change the !== undefined check in matches()
3818
3831
  ...filter.systemCostCategory !== void 0 ? { systemCostCategory: filter.systemCostCategory } : {},
3819
- // Stryker disable next-line ConditionalExpression -- spreading { enforcedOnly: undefined } does not change the === true check in matches()
3832
+ // Stryker disable next-line ConditionalExpression: spreading { enforcedOnly: undefined } does not change the === true check in matches()
3820
3833
  ...filter.enforcedOnly !== void 0 ? { enforcedOnly: filter.enforcedOnly } : {},
3821
3834
  status: filter.status ?? [...BALANCE_STATUSES2],
3822
3835
  from: filter.from,
@@ -4002,9 +4015,9 @@ var MeteringInterceptor = class {
4002
4015
  const context = await this.resolveContext(executionContext, config, enrichment);
4003
4016
  return this.metering.record({
4004
4017
  usage,
4005
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { preset: undefined } is equivalent to {} because MeteringService checks preset !== undefined
4018
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { preset: undefined } is equivalent to {} because MeteringService checks preset !== undefined
4006
4019
  ...config.preset !== void 0 ? { preset: config.preset } : {},
4007
- // Stryker disable next-line ConditionalExpression -- CE true: spreading { tags: undefined } is equivalent to {} because MeteringService checks tags !== undefined
4020
+ // Stryker disable next-line ConditionalExpression: CE true: spreading { tags: undefined } is equivalent to {} because MeteringService checks tags !== undefined
4008
4021
  context: { ...context, enforce: true, isSystemCost: config.isSystemCost ?? false, ...config.tags !== void 0 ? { tags: config.tags } : {} }
4009
4022
  });
4010
4023
  }
@@ -4332,24 +4345,24 @@ function preset(provider, normalizer, ratingMode) {
4332
4345
  return { provider, normalizer, ratingMode };
4333
4346
  }
4334
4347
  var providerPresets = {
4335
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests that exercise the full preset→MeteringService→UsageRecord path
4348
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests that exercise the full preset→MeteringService→UsageRecord path
4336
4349
  openaiChat: preset("openai", normalizeOpenAiChatUsage, "rate-table"),
4337
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests
4350
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests
4338
4351
  openaiResponses: preset("openai", normalizeOpenAiResponsesUsage, "rate-table"),
4339
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests
4352
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests
4340
4353
  azureOpenai: preset("azure-openai", normalizeOpenAiChatUsage, "rate-table"),
4341
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests
4354
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests
4342
4355
  anthropic: preset("anthropic", normalizeAnthropicUsage, "rate-table"),
4343
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests
4356
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests
4344
4357
  gemini: preset("gemini", normalizeGeminiUsage, "rate-table"),
4345
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests
4358
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests
4346
4359
  vertex: preset("vertex", normalizeGeminiUsage, "rate-table"),
4347
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests
4360
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests
4348
4361
  bedrock: preset("bedrock", normalizeBedrockConverseUsage, "rate-table"),
4349
- // Stryker disable next-line StringLiteral -- provider ID; only observable via integration tests
4362
+ // Stryker disable next-line StringLiteral: provider ID; only observable via integration tests
4350
4363
  mistral: preset("mistral", normalizeMistralUsage, "rate-table"),
4351
4364
  vercelAiSdk: preset("openai", normalizeVercelAiSdkUsage, "rate-table"),
4352
- // Stryker disable next-line StringLiteral -- provider ID and rating mode; only observable via integration tests
4365
+ // Stryker disable next-line StringLiteral: provider ID and rating mode; only observable via integration tests
4353
4366
  openrouter: preset("openrouter", normalizeOpenRouterUsage, "provider-reported"),
4354
4367
  /** A custom OpenAI-compatible provider (DeepSeek, xAI, Groq, …). */
4355
4368
  openaiCompatible: (id) => preset(id, normalizeOpenAiCompatibleUsage, "rate-table")
@@ -332,9 +332,9 @@ function normalizeMistralUsage(raw) {
332
332
  provider: "mistral",
333
333
  model: str(response.model) ?? "",
334
334
  operation: "chat",
335
- // Stryker disable next-line StringLiteral -- provider and field names in error messages are internal diagnostics
335
+ // Stryker disable next-line StringLiteral: provider and field names in error messages are internal diagnostics
336
336
  inputTokens: requireNum(usage.prompt_tokens, "mistral", "usage.prompt_tokens"),
337
- // Stryker disable next-line StringLiteral -- provider and field names in error messages are internal diagnostics
337
+ // Stryker disable next-line StringLiteral: provider and field names in error messages are internal diagnostics
338
338
  outputTokens: requireNum(usage.completion_tokens, "mistral", "usage.completion_tokens"),
339
339
  raw: usage
340
340
  });
@@ -330,9 +330,9 @@ function normalizeMistralUsage(raw) {
330
330
  provider: "mistral",
331
331
  model: str(response.model) ?? "",
332
332
  operation: "chat",
333
- // Stryker disable next-line StringLiteral -- provider and field names in error messages are internal diagnostics
333
+ // Stryker disable next-line StringLiteral: provider and field names in error messages are internal diagnostics
334
334
  inputTokens: requireNum(usage.prompt_tokens, "mistral", "usage.prompt_tokens"),
335
- // Stryker disable next-line StringLiteral -- provider and field names in error messages are internal diagnostics
335
+ // Stryker disable next-line StringLiteral: provider and field names in error messages are internal diagnostics
336
336
  outputTokens: requireNum(usage.completion_tokens, "mistral", "usage.completion_tokens"),
337
337
  raw: usage
338
338
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bymax-one/nest-ai-tokens",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "AI token metering & usage-based billing for NestJS 11: provider usage normalizer (×9), versioned effective-dated pricing, immutable append-only ledger, prepaid wallets, multi-dimension budgets, and markup/margin. Zero runtime dependencies.",
5
5
  "author": "Bymax One <support@bymax.one>",
6
6
  "license": "MIT",
@@ -76,6 +76,7 @@
76
76
  "scripts": {
77
77
  "build": "pnpm clean && tsup",
78
78
  "check:exports": "attw --pack . --profile strict",
79
+ "check:mutants": "node scripts/check-mutation-directives.mjs",
79
80
  "check:published": "node scripts/check-published-surface.mjs",
80
81
  "check:runtime": "node scripts/check-consumer-runtime.mjs",
81
82
  "clean": "node -e \"const {rmSync}=require('node:fs');for(const d of ['dist','coverage'])rmSync(d,{recursive:true,force:true})\"",
@@ -84,10 +85,10 @@
84
85
  "lint": "eslint src",
85
86
  "lint:fix": "eslint src --fix",
86
87
  "mutation": "stryker run",
88
+ "mutation:full": "node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run",
87
89
  "mutation:dry-run": "stryker run --dryRunOnly",
88
- "mutation:incremental": "stryker run --incremental",
89
90
  "prepare": "prisma generate",
90
- "prepublishOnly": "pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published",
91
+ "prepublishOnly": "pnpm clean && pnpm typecheck && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published",
91
92
  "release": "npm publish --provenance --access public",
92
93
  "size": "node scripts/check-size.mjs",
93
94
  "test": "jest",