@ultimat3/core 16.0.0 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -125,7 +125,7 @@ shape against a locally declared sample interface for exactly that reason.
125
125
  | the five above, composed into one typed-client call | `client-flight.ts` + `client-wire.ts` | `@ultimat3/action` and `@ultimat3/query` both project a typed client and are both tier 3, so neither could import the other: it shipped as a byte-identical 288-line + 85-line copy in each, policed by a `client-twin.test.ts` in both. Both packages re-export these names, so their public surface is unchanged. Declares NO code of its own — `X_SUPERSEDED`, `X_TIMEOUT` and `X_FLIGHT_GATE_OVERLOADED` are already here |
126
126
  | is this `unknown` a keyed record? | `json-object.ts` | `isJsonObject`, which was the same three terms in both those packages' `stable.ts`. A `Date` and a class instance PASS: it narrows a shape, it does not certify provenance |
127
127
  | a value that must not be printed | `secret.ts` | redacted by VALUE; `revealSecret()` is the one way out, on purpose greppable |
128
- | an `Intl` formatter cache | `intl-cache.ts` (`cachedFormatter`, `canonicalLocale`, `MAX_CACHED_FORMATTERS`) | a locale and a zone arrive from a request header, so the key must be canonical AND the cache bounded — never a second copy of either half |
128
+ | an `Intl` formatter cache, and the screen in front of it | `intl-cache.ts` (`cachedFormatter`, `canonicalLocale`, `assertLocale`, `MAX_CACHED_FORMATTERS`) | a locale and a zone arrive from a request header, so the tag must be REFUSED when it is not a tag (`X_LOCALE_INVALID`), the key must be canonical AND the cache bounded — never a second copy of any of the three |
129
129
  | the committed encrypted values | `secrets.ts` (envelope) + `secrets-store.ts` (files, `installSecrets`) | plaintext is a flat map of ENV NAMES; there is no `secrets.get()` |
130
130
 
131
131
  `installSecrets()` is the ONLY path from `secrets.enc.json` to an app value, and it lands in
@@ -144,6 +144,15 @@ and live in one file for that reason: a canonical key bounds nothing (an unknown
144
144
  value survives canonicalization as a distinct string) and the cap alone lets one locale evict
145
145
  itself under three spellings. Never build an `Intl` formatter on a caller string without both.
146
146
 
147
+ `assertLocale` and `X_LOCALE_INVALID` moved here from `@ultimat3/time` in 16.x, for the third
148
+ time the same argument was made: `@ultimat3/money` handed a malformed `Accept-Language` tag
149
+ straight to `Intl.NumberFormat` at four entry points and let a bare `RangeError` out, and copying
150
+ time's screen down would have been a second answer to one question. Validating and keying are ONE
151
+ step — `getCanonicalLocales` runs exactly the check `supportedLocalesOf` throws on and hands back
152
+ the spelling the cache keys on — which is why the screen lives in this file and not beside it. A
153
+ code has one declaration: `X_LOCALE_INVALID` left `TIME_ERROR_CODES` in the same change, or
154
+ `registerErrorCodes` would raise `X_ERROR_CODE_DUPLICATE` the moment both packages loaded.
155
+
147
156
  `secrets-errors.ts` registers its seven codes through `registerErrorCodes()` rather than joining
148
157
  `CORE_CODE_TITLES` — the codes and the module that throws them ship together, and `registerErrorCodes`
149
158
  is the one mechanism that raises `X_ERROR_CODE_DUPLICATE` if anything else claims one. Consequence
@@ -218,6 +227,28 @@ the layer imports anything but this package, nothing runs at import time, and ev
218
227
  non-determinism — the roll, the sleep, the clock, the timer — is injected, because a schedule
219
228
  provable only by waiting is a schedule no test pins.
220
229
 
230
+ **`backoffDelay` REFUSES a non-finite bound, `As of 2026-08-26`, and that is a behaviour change.**
231
+ It used to answer `0`, on the argument that 0 is "wrong loudly". It is not: measured,
232
+ `retry({ attempts: 5, max: NaN })` slept `[0, 0, 0, 0]` and `factor: NaN` slept `[1000, 0, 0, 0]` —
233
+ a retry loop with no wait at all, which is the failure backoff exists to prevent, on the tree's ONE
234
+ curve (`bun run flight-copies`), reaching every retry in the framework. `attempt`, `base`, `max`
235
+ and `factor` each go through `finiteOption` before the clamps, because `Math.max`, `Math.min` and
236
+ `Math.trunc` propagate rather than validate and `Math.min(raw, Infinity)` is `Infinity`. The
237
+ `return 0` below them stays and is still reachable, by exactly one route: `factor ** (step - 1)`
238
+ overflows around attempt 1030 and `0 * Infinity` is `NaN` — a zero base is a caller asking for no
239
+ wait, which is the answer it gets.
240
+
241
+ **`finiteOption` / `finiteCount` (`finite-option.ts`) are the framework's ONE screen for a numeric
242
+ option, `As of 2026-08-26`.** They landed here because `@ultimat3/jobs`, `@ultimat3/realtime` and
243
+ `@ultimat3/query` each held a byte-identical copy; all three were deleted and now import these.
244
+ `finiteCount(subject, option, value, min)` takes `min: 0 | 1` as a DEFAULT PARAMETER rather than
245
+ shipping a third `finitePositive`, because only the caller knows what zero means —
246
+ `requestTimeoutMs: 0` is "no deadline", a pool `max: 0` is a pool nothing can run on.
247
+ `bun run finite-bounds` is the ratchet, and it recognises a repair by the SHAPE OF THE CALL: a
248
+ package screen that does not carry `Finite` in its name is invisible to it, which is why
249
+ `assertFiniteImageQuality`, `assertFiniteOtlpBound` and their siblings in five other packages are
250
+ spelled that way.
251
+
221
252
  Two rules that are not preferences. `backoffDelay` clamps to `max` **before** jitter: capping after
222
253
  turns `full` into a distribution whose upper half is a single value at `max`, which is the
223
254
  correlation jitter exists to remove. `createFlightGate` **hands** its slot to a waiter rather than
package/README.md CHANGED
@@ -316,7 +316,9 @@ nothing and still reports healthy.
316
316
  - **Sampling is honoured, not just propagated.** `startSpan` takes the parent's bit when there is
317
317
  one, else asks the `Sampler`; `span.end()` exports nothing when the bit is 0. The default reads
318
318
  `OTEL_TRACES_SAMPLER` / `OTEL_TRACES_SAMPLER_ARG` at the first span, and
319
- `configureTelemetry({ sampler })` replaces it.
319
+ `configureTelemetry({ sampler })` replaces it. `shouldSample` is handed the TRACE ID of the span
320
+ being created, so `traceidratio` decides once per trace by hashing it — a roll per span left one
321
+ trace half exported, which is orphan children under a root the collector never saw.
320
322
  - **The OTLP exporters ship**, speaking OTLP/HTTP JSON, no dependency:
321
323
  `otlpSpanExporter({ endpoint })` (batched, with `flush()` / `shutdown()`) and
322
324
  `otlpMetricExporter({ endpoint })`. Both default to `OTEL_EXPORTER_OTLP_ENDPOINT`;
@@ -457,13 +459,16 @@ never a silently wrong page.
457
459
  ## One bounded cache for every `Intl` formatter
458
460
 
459
461
  ```ts
460
- import { cachedFormatter, canonicalLocale } from '@ultimat3/core';
462
+ import { assertLocale, cachedFormatter } from '@ultimat3/core';
461
463
 
462
464
  const cache = new Map<string, Intl.NumberFormat>();
463
465
 
464
466
  export function euroFormatter(locale: string): Intl.NumberFormat {
465
- // `EN-us` and `en-latn-us` collapse to one key, so one locale cannot evict itself.
466
- const tag = canonicalLocale(locale) ?? locale;
467
+ // Validates AND canonicalizes: `EN-us` and `en-latn-us` collapse to one key, and a tag `Intl`
468
+ // cannot parse is `X_LOCALE_INVALID` here rather than a bare `RangeError` out of the constructor
469
+ // below. `canonicalLocale(locale) ?? locale` is the same call with the refusal deleted — it is
470
+ // what `@ultimat3/money` did, and an `Accept-Language` value of `en_US` took the request down.
471
+ const tag = assertLocale(locale);
467
472
  return cachedFormatter(
468
473
  cache,
469
474
  `${tag}|EUR`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/core",
3
- "version": "16.0.0",
3
+ "version": "17.0.0",
4
4
  "description": "Ultimate's foundation: errors, context, env, config, clock, ids, logging, telemetry, lifecycle",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/backoff.ts CHANGED
@@ -3,6 +3,8 @@
3
3
  // (equal jitter), `@ultimat3/ai` (full jitter, `Math.random` inline and so untestable),
4
4
  // `@ultimat3/realtime` (full jitter, 0-based attempt) and `@ultimat3/db` (no backoff at all).
5
5
 
6
+ import { finiteOption } from './finite-option';
7
+
6
8
  export type BackoffCurve = 'exponential' | 'linear' | 'fixed';
7
9
 
8
10
  /**
@@ -12,6 +14,9 @@ export type BackoffCurve = 'exponential' | 'linear' | 'fixed';
12
14
  */
13
15
  export type JitterMode = 'full' | 'equal' | 'none';
14
16
 
17
+ /** Named in every refusal below, so a reader knows which call built the schedule. */
18
+ const SUBJECT = 'backoffDelay';
19
+
15
20
  /** Injected everywhere. A delay only provable by observing a range is a delay no test pins. */
16
21
  export type Random = () => number;
17
22
 
@@ -38,19 +43,26 @@ export interface BackoffOptions {
38
43
  * jitter exists to remove.
39
44
  */
40
45
  export function backoffDelay(options: BackoffOptions): number {
41
- const step = Math.max(1, Math.trunc(options.attempt));
42
- const base = Math.max(0, options.base);
43
- const factor = options.factor ?? 2;
46
+ // Every bound is REFUSED before it is clamped, because the clamps are not validators: measured,
47
+ // `retry({ attempts: 5, max: NaN })` slept `[0, 0, 0, 0]` and `factor: NaN` slept
48
+ // `[1000, 0, 0, 0]` — `Math.max`, `Math.min` and `Math.trunc` each propagate it,
49
+ // `Math.min(raw, Infinity)` is `Infinity`, and the `return 0` below then turns the whole
50
+ // schedule into a spin.
51
+ const step = Math.max(1, Math.trunc(finiteOption(SUBJECT, 'attempt', options.attempt)));
52
+ const base = Math.max(0, finiteOption(SUBJECT, 'base', options.base));
53
+ const max = finiteOption(SUBJECT, 'max', options.max);
54
+ const factor = finiteOption(SUBJECT, 'factor', options.factor ?? 2);
44
55
 
45
56
  let raw: number;
46
57
  if (options.curve === 'fixed') raw = base;
47
58
  else if (options.curve === 'linear') raw = base * step;
48
59
  else raw = base * factor ** (step - 1);
49
60
 
50
- const capped = Math.max(0, Math.min(raw, options.max));
51
- // A NaN reaches here from an unvalidated config value, and `setTimeout(NaN)` fires IMMEDIATELY —
52
- // a retry loop with no wait at all, which is the failure mode backoff exists to prevent. 0 is
53
- // wrong too, but it is wrong loudly: the next attempt still happens once, not in a tight spin.
61
+ const capped = Math.max(0, Math.min(raw, max));
62
+ // Still reachable with four finite inputs, and only one way: `factor ** (step - 1)` overflows to
63
+ // `Infinity` around attempt 1030, and `0 * Infinity` is `NaN`. A zero base is a caller asking
64
+ // for no wait, so 0 is the answer it asked for — it is no longer the answer an unvalidated
65
+ // config value gets, which is what this line used to be.
54
66
  if (!Number.isFinite(capped)) return 0;
55
67
 
56
68
  const roll = options.random ?? Math.random;
package/src/context.ts CHANGED
@@ -34,6 +34,7 @@ import { type Actor, anonymousActor } from './actor';
34
34
  import { asyncContext } from './async-context';
35
35
  import { type Clock, systemClock } from './clock';
36
36
  import { UltimateError } from './errors';
37
+ import { finiteOption } from './finite-option';
37
38
  import { traceId as newTraceId, uuid } from './ids';
38
39
  import { type Logger, logger as rootLogger, setLoggerContextFields } from './logger';
39
40
  import { type Role, resolveRole } from './roles';
@@ -173,7 +174,7 @@ export function createContext(init: CtxInit = {}): Ctx {
173
174
  now: () => clock.now(),
174
175
  logger: base.child({ requestId, traceId: trace }),
175
176
  signal: init.signal ?? neverAborted,
176
- deadlineAt: init.deadlineAt ?? null,
177
+ deadlineAt: screenDeadline(init.deadlineAt),
177
178
  };
178
179
  // A registered service (`defineService`) closes over the ctx it is built for — actor, clock,
179
180
  // tz — so it has to run HERE, against this exact call's fields, rather than once at boot and
@@ -231,6 +232,35 @@ export function hasContext(): boolean {
231
232
  return tryUseContext() !== undefined;
232
233
  }
233
234
 
235
+ /**
236
+ * The tighter of two instants, where absent means "no bound" — the bounded form
237
+ * `lifecycle.ts`'s drain budget uses, over two optional values instead of one. Returns
238
+ * `undefined` rather than `null` because that is what `CtxInit.deadlineAt` spells.
239
+ */
240
+ function earliest(left: number | undefined, right: number | null | undefined): number | undefined {
241
+ if (left === undefined) return right ?? undefined;
242
+ if (right === undefined || right === null) return left;
243
+ return Math.min(left, right);
244
+ }
245
+
246
+ /**
247
+ * The one screen over `deadlineAt`, at both boundaries that can set it. A deadline is an instant,
248
+ * not a count, so `finiteOption` rather than `finiteCount` — a clock epoch is legitimately large
249
+ * and a test clock's is legitimately small.
250
+ *
251
+ * WHY IT IS REFUSED AND NOT CLAMPED, in this file specifically: `Math.min` PROPAGATES `NaN`, so one
252
+ * non-finite side poisons the child's deadline as well as its own. `remainingBudgetMs` then asks
253
+ * `left >= 1` of a `NaN`, gets `false`, and answers `undefined` — the same answer it gives for "no
254
+ * deadline at all". So the `x-request-timeout-ms` header is silently omitted and the next hop falls
255
+ * back to its OWN configured budget, which `request-budget.ts`'s own header calls "the exact
256
+ * failure this header exists to prevent". A deadline that quietly stops bounding anything is the
257
+ * dangerous direction, and it is the direction `??` cannot see, because `NaN` is not nullish.
258
+ */
259
+ function screenDeadline(value: number | null | undefined): number | null {
260
+ if (value === undefined || value === null) return null;
261
+ return finiteOption('the request context', 'deadlineAt', value);
262
+ }
263
+
234
264
  /**
235
265
  * Derive a narrowed context — impersonation, a locale switch, a per-step abort signal.
236
266
  * `requestId` is deliberately not patchable: one request, one id.
@@ -256,8 +286,10 @@ export function withChildContext<T>(patch: CtxPatch, fn: () => T): T {
256
286
  logger: patch.logger ?? parent.logger,
257
287
  // One request, one budget: a child scope inherits the deadline for the same reason it
258
288
  // inherits `requestId`. A patch may SHORTEN it (a step with its own budget); nothing here
259
- // lengthens it, because the socket the parent is answering does not move.
260
- deadlineAt: patch.deadlineAt ?? parent.deadlineAt ?? undefined,
289
+ // lengthens it, because the socket the parent is answering does not move. `??` alone said
290
+ // that and did not do it — a patched hour replaced a parent's second outright, and
291
+ // `remainingBudgetMs` then put the hour on `x-request-timeout-ms` for the next hop.
292
+ deadlineAt: earliest(screenDeadline(patch.deadlineAt) ?? undefined, parent.deadlineAt),
261
293
  signal: patch.signal ?? parent.signal,
262
294
  services: { ...carried, ...(patch.services ?? {}) },
263
295
  });
@@ -52,6 +52,9 @@ const CORE_CODE_TITLES = {
52
52
  X_IMAGE_UNSUPPORTED: 'the built-in image pipeline cannot read or write this format',
53
53
  X_INTERNAL: 'unexpected internal framework error',
54
54
  X_INVARIANT: 'invariant violated',
55
+ // Owned here rather than by `@ultimat3/time`, which declared it until 16.x: `@ultimat3/money`
56
+ // needs the same screen and tier 1 may not import sideways. One code, one declaration.
57
+ X_LOCALE_INVALID: 'not a well-formed BCP 47 tag',
55
58
  X_METRIC_CARDINALITY:
56
59
  'a metric exceeded its series ceiling and is folding into one overflow series',
57
60
  X_METRIC_NAME_INVALID:
@@ -106,9 +106,21 @@ export function registerErrorRetry(retries: Readonly<Record<string, ErrorRetry>>
106
106
  }
107
107
  }
108
108
 
109
- /** Test seam. Production registers once at boot and never unregisters. */
110
- export function resetErrorRetry(): void {
109
+ /**
110
+ * Test seam. Production registers once at boot and never unregisters.
111
+ *
112
+ * `restore` is not a nicety: this map is PROCESS-WIDE and every package fills it at module
113
+ * evaluation, so a bare `clear()` in one file's `afterEach` deletes registrations that file never
114
+ * made. Bun runs one process for the whole suite and `scripts/test-setup.ts` imports packages
115
+ * before any test runs, so their module bodies never run again to repair it — measured,
116
+ * `@ultimat3/policy`'s codes read as unclassified in every file loaded after core's tests, and
117
+ * `@ultimat3/db`, `@ultimat3/ai` and `@ultimat3/jobs` each carry a `beforeEach` that re-registers
118
+ * their own table for exactly this reason. Pass `registeredErrorRetry()` captured at module scope
119
+ * and the wipe is scoped to what the test added.
120
+ */
121
+ export function resetErrorRetry(restore: Readonly<Record<string, ErrorRetry>> = {}): void {
111
122
  REGISTERED.clear();
123
+ for (const [code, retry] of Object.entries(restore)) REGISTERED.set(code, retry);
112
124
  }
113
125
 
114
126
  /**
@@ -0,0 +1,54 @@
1
+ // Single responsibility: the one refusal the framework makes for a numeric option that is not a
2
+ // number. Tier 0 because twenty-one packages need it — `jobs`, `realtime` and `query` each carried
3
+ // a byte-identical copy, the shape three backoff curves and three SQL escapes already took.
4
+ // `bun run finite-bounds` is the ratchet; this is the repair it recognises.
5
+
6
+ import { assert } from './assert';
7
+
8
+ /**
9
+ * The option, when it is a real number. Returns it so a call site stays one expression:
10
+ * `this.#capacity = finiteOption('ChangeBuffer', 'capacity', options.capacity ?? 1024)`.
11
+ *
12
+ * WHY IT TAKES A FUNCTION AT ALL, since every part of the job looks already done by an operator:
13
+ * `??` guards NULLISH and `NaN` is not nullish, so `Number(process.env.X)` on an unset variable, a
14
+ * `parseInt` of a typo and a JSON `null`-turned-`NaN` all walk straight past the default. And
15
+ * `Math.max` / `Math.min` / `Math.floor` PROPAGATE it rather than validating, so none of the three
16
+ * is a screen either — `Math.min(raw, Infinity)` is `Infinity`. What the value then bounds decides
17
+ * WHICH failure you get, and all four have shipped: a comparison that reads false forever, an
18
+ * `Array.from({ length: NaN })` that is `[]`, a `setTimeout(fn, NaN)` that is `setTimeout(fn, 0)`,
19
+ * and a loop that never terminates. Several packages' comments point here for this argument.
20
+ */
21
+ export function finiteOption(subject: string, option: string, value: number): number {
22
+ assert(
23
+ Number.isFinite(value),
24
+ `${subject} ${option} is ${String(value)}, so every comparison against it is false and the bound it sets does not exist`,
25
+ `pass a finite ${option} to ${subject}, or omit it and take the default — Math.max/Math.min/Math.floor do not validate it, they propagate it`,
26
+ );
27
+ return value;
28
+ }
29
+
30
+ /**
31
+ * The same rule where the option counts THINGS — rows, bytes, entries, milliseconds, slots — and a
32
+ * fraction is as wrong as a `NaN`: `Array.from({ length: 2.5 })` throws, a `slice` truncates, and
33
+ * above 2^53 a double cannot name its own successor, so a "count" up there is already rounded.
34
+ * `Number.isSafeInteger`, which is what `@ultimat3/schema` demands of an integer at the wire.
35
+ *
36
+ * `min` is the caller's because only the caller knows what zero MEANS: `requestTimeoutMs: 0` is
37
+ * "no deadline" and `maxInflight: 0` is "never shed", both decisions the code reads, while
38
+ * `max: 0` connections is a pool nothing can run on. A helper that picked one would be wrong at
39
+ * half the call sites, and this is deliberately the only knob it takes — a third function for
40
+ * "positive" would be the copy this file exists to prevent.
41
+ */
42
+ export function finiteCount(
43
+ subject: string,
44
+ option: string,
45
+ value: number,
46
+ min: 0 | 1 = 0,
47
+ ): number {
48
+ assert(
49
+ Number.isSafeInteger(value) && value >= min,
50
+ `${subject} ${option} is ${String(value)}, and it counts things: it must be a whole number of ${min === 1 ? 'at least 1' : '0 or more'}, or the bound it sets is not one`,
51
+ `pass a whole ${option} to ${subject} — and parse an environment value before you pass it, because Number(process.env.…) is NaN when the variable is unset and Math.floor does not repair that`,
52
+ );
53
+ return value;
54
+ }
package/src/ids.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  import { type Clock, systemClock } from './clock';
5
5
  import { describeValue } from './error-render';
6
6
  import { UltimateError } from './errors';
7
+ import { finiteCount } from './finite-option';
7
8
 
8
9
  /** Nominal typing without a runtime cost. `Brand<string, 'post'>` never mixes with `'user'`. */
9
10
  export type Brand<T, K extends string> = T & { readonly __brand: K };
@@ -39,8 +40,13 @@ function seedCounter(): number {
39
40
  return (((bytes[0] ?? 0) << 8) | (bytes[1] ?? 0)) & COUNTER_SEED_MASK;
40
41
  }
41
42
 
43
+ /**
44
+ * `new Uint8Array(NaN)` is a zero-length array, not a throw, so an unscreened length made this
45
+ * answer `''` — an id that is no id, minted silently. `min: 1`: zero bytes of randomness is the
46
+ * same empty string, and every caller here (`uuid`, `traceId`, `spanId`) wants a width.
47
+ */
42
48
  export function randomHex(byteLength: number): string {
43
- const bytes = randomBytes(byteLength);
49
+ const bytes = randomBytes(finiteCount('randomHex', 'byteLength', byteLength, 1));
44
50
  let out = '';
45
51
  for (const byte of bytes) {
46
52
  out += HEX[byte >> 4];
@@ -109,9 +115,15 @@ export function uuidTimestamp(id: string): Date {
109
115
  return new Date(Number.parseInt(id.slice(0, 8) + id.slice(9, 13), 16));
110
116
  }
111
117
 
112
- /** URL-safe random id. Not sortable — use it for tokens and slugs, never primary keys. */
118
+ /**
119
+ * URL-safe random id. Not sortable — use it for tokens and slugs, never primary keys.
120
+ *
121
+ * The default is not a screen: `??` guards nullish and `NaN` is not nullish, so
122
+ * `nanoid(Number(process.env.ID_LENGTH))` on an unset variable walked past `21` and answered the
123
+ * empty string. `min: 1` for the reason `randomHex` gives.
124
+ */
113
125
  export function nanoid(length = 21): string {
114
- const bytes = randomBytes(length);
126
+ const bytes = randomBytes(finiteCount('nanoid', 'length', length, 1));
115
127
  let out = '';
116
128
  for (const byte of bytes) out += NANO_ALPHABET[byte & 63] as string;
117
129
  return out;
@@ -3,6 +3,7 @@
3
3
  // copy: responsive variants, blur placeholders and PWA icons are the same three steps with
4
4
  // different numbers, and a framework that generated them twice would drift twice.
5
5
 
6
+ import { assert } from '../assert';
6
7
  import { composeOnto, layOut, type ResizeSpec } from './canvas';
7
8
  import { imageFromBunError, imageUnsupported } from './errors';
8
9
  import { unshared } from './png-bytes';
@@ -34,6 +35,27 @@ export const canEncode = (format: string): format is EncodableFormat =>
34
35
  /** 1-100, lossy formats only. Bun's own default, pinned here so output cannot drift with it. */
35
36
  export const DEFAULT_IMAGE_QUALITY = 80;
36
37
 
38
+ /**
39
+ * The encoder CLAMPS and never refuses, which is why this is a screen and not a comment. Exported
40
+ * because `@ultimat3/storage` mints a variant KEY from the same number without ever reaching this
41
+ * pipeline — two screens that refuse different qualities would mint `q150` keys for bytes this
42
+ * encoder goes on to refuse, which is the dead-on-arrival shape `buildSignedUrl`'s own
43
+ * `finiteCount` screen already exists for — that package held a private `assertFiniteSignedUrlBound`
44
+ * until this sweep collapsed it onto the tier-0 check. Measured
45
+ * on a 3x2 PNG re-encoded as JPEG: quality 80 wrote 663 bytes, and `NaN`, `-5` and `0` each wrote
46
+ * 635 — the worst encoding the codec can make — while `Infinity` and `1e9` each wrote 719. Every
47
+ * one of them silently. `Number(url.searchParams.get('q'))` is `NaN` when the parameter is absent,
48
+ * so the degraded image is the one an unparameterised URL gets.
49
+ */
50
+ export const assertFiniteImageQuality = (quality: number): number => {
51
+ assert(
52
+ Number.isInteger(quality) && quality >= 1 && quality <= 100,
53
+ `image quality is ${String(quality)}, and the encoder clamps rather than refusing: it must be a whole number from 1 to 100`,
54
+ `pass a whole quality from 1 to 100 — transformImageBytes(bytes, { quality: ${String(DEFAULT_IMAGE_QUALITY)} }) — and parse a query value before you pass it`,
55
+ );
56
+ return quality;
57
+ };
58
+
37
59
  const encodeFix =
38
60
  "request 'png', 'jpeg' or 'webp', or route the transform through an ImageTransformDriver (a " +
39
61
  'CDN or an external encoder) that can produce it';
@@ -86,7 +108,7 @@ export async function transformImageBytes(
86
108
  if (format !== undefined && !canEncode(format)) {
87
109
  throw imageUnsupported(`encoding ${format} is not built in`, encodeFix, { format });
88
110
  }
89
- const quality = spec.quality ?? DEFAULT_IMAGE_QUALITY;
111
+ const quality = assertFiniteImageQuality(spec.quality ?? DEFAULT_IMAGE_QUALITY);
90
112
  const source = await run('reading the image header', () => bunImage(bytes).metadata());
91
113
  const output: EncodableFormat = format ?? (canEncode(source.format) ? source.format : 'png');
92
114
  const layout = layOut(source, spec);
package/src/index.ts CHANGED
@@ -390,6 +390,7 @@ export {
390
390
  writeMasterKeyFile,
391
391
  writeSecretsFile,
392
392
  } from './exports/secrets';
393
+ export { finiteCount, finiteOption } from './finite-option';
393
394
  export type {
394
395
  FlightGate,
395
396
  FlightGateLimits,
@@ -432,6 +433,7 @@ export type {
432
433
  ImageTransformSpec,
433
434
  } from './image/pipeline';
434
435
  export {
436
+ assertFiniteImageQuality,
435
437
  blurDataUrl,
436
438
  canDecode,
437
439
  canEncode,
@@ -453,7 +455,13 @@ export {
453
455
  rasterFrom,
454
456
  } from './image/raster';
455
457
  export { impersonate, impersonationReason, isImpersonating } from './impersonate';
456
- export { cachedFormatter, canonicalLocale, MAX_CACHED_FORMATTERS } from './intl-cache';
458
+ export {
459
+ assertLocale,
460
+ cachedFormatter,
461
+ canonicalLocale,
462
+ localeInvalid,
463
+ MAX_CACHED_FORMATTERS,
464
+ } from './intl-cache';
457
465
  export { isJsonObject } from './json-object';
458
466
  export type {
459
467
  HealthPayload,
package/src/intl-cache.ts CHANGED
@@ -1,8 +1,11 @@
1
- // One bounded cache, on one canonical key, for every `Intl` formatter the framework builds.
1
+ // One bounded cache, on one canonical key, and one screen, for every `Intl` formatter the
2
+ // framework builds.
2
3
  // A locale and a zone both arrive from a request header, so an unbounded `Map` keyed on the
3
4
  // caller's spelling is memory the client chooses — 31 MB and 55.1 MB, measured `As of 2026-08` and
4
5
  // written up in the README. The bound and the canonical key are two halves of ONE rule.
5
6
 
7
+ import { UltimateError } from './errors';
8
+
6
9
  /**
7
10
  * Above the full canonical IANA set (445 zones as of tzdata 2025) so a correct app never evicts,
8
11
  * and small enough that the worst case is a few megabytes rather than a leak. A miss costs one
@@ -41,3 +44,41 @@ export function canonicalLocale(locale: string): string | undefined {
41
44
  return undefined;
42
45
  }
43
46
  }
47
+
48
+ /**
49
+ * A tag `Intl` cannot parse. Distinct from `@ultimat3/i18n`'s `X_LOCALE_UNSUPPORTED`, which is a
50
+ * well-formed tag outside the app's supported set — this one is not a tag at all, and a raw
51
+ * `RangeError` out of a formatter says nothing about which caller supplied it.
52
+ */
53
+ export function localeInvalid(locale: string): UltimateError {
54
+ return new UltimateError({
55
+ code: 'X_LOCALE_INVALID',
56
+ cause: `"${locale}" is not a well-formed BCP 47 language tag`,
57
+ fix: "pass a tag like 'en', 'en-GB' or 'de-DE' — screen a header-supplied value with Intl.DateTimeFormat.supportedLocalesOf([tag]) before it reaches a formatter",
58
+ });
59
+ }
60
+
61
+ /**
62
+ * The canonical spelling of a well-formed tag, or `X_LOCALE_INVALID`. The ONE screen a
63
+ * caller-supplied BCP 47 tag passes before it reaches an `Intl` constructor.
64
+ *
65
+ * Validating and keying are one step, which is why this lives beside the cache rather than in
66
+ * either caller: `getCanonicalLocales` runs exactly the structural check
67
+ * `Intl.DateTimeFormat.supportedLocalesOf` throws on — what the `fix:` above tells the caller to
68
+ * run — and unlike it hands back the spelling every formatter cache keys on, so `EN-us` and
69
+ * `en-US` cannot mint two entries for one locale.
70
+ *
71
+ * It is tier 0 for the reason `cachedFormatter` is: `@ultimat3/time` and `@ultimat3/money` both
72
+ * need it and `money -> time` is a sideways import `bun run boundaries` refuses. Money was the
73
+ * last package still letting the tag through — a `RangeError` off an `Accept-Language` header —
74
+ * on the argument that this seam "decides a cache key, never whether a locale is acceptable",
75
+ * which is true of the cache and was never an argument for passing the tag on.
76
+ *
77
+ * Well-formed but unknown to this runtime's ICU (`zz`) is NOT refused: `Intl` falls back for
78
+ * those, and a user carrying a locale the runtime has no data for must still get a rendered page.
79
+ */
80
+ export function assertLocale(locale: string): string {
81
+ const tag = canonicalLocale(locale);
82
+ if (tag === undefined) throw localeInvalid(locale);
83
+ return tag;
84
+ }
package/src/lifecycle.ts CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { type Clock, systemClock } from './clock';
6
6
  import { UltimateError } from './errors';
7
+ import { finiteCount } from './finite-option';
7
8
  import { settleWithin } from './lifecycle-deadline';
8
9
  import { lifecycleDrained } from './lifecycle-errors';
9
10
  import { type LogFields, type Logger, logger as rootLogger } from './logger';
@@ -49,6 +50,8 @@ export interface LifecycleOptions {
49
50
  * lapses and another worker re-claims it, which is what at-least-once already promises. The
50
51
  * alternative is not "the job finishes": it is the same duplicate, delivered by SIGKILL at the
51
52
  * kubelet's grace period, with no log line naming what overran.
53
+ *
54
+ * Screened where it is assigned: a whole number of milliseconds, 0 or more. `0` is "drain now".
52
55
  */
53
56
  readonly deadlineMs?: number | undefined;
54
57
  readonly clock?: Clock | undefined;
@@ -119,7 +122,15 @@ let idleWaiters: (() => void)[] = [];
119
122
  const readiness = new Map<string, ReadinessCheck>();
120
123
 
121
124
  export function configureLifecycle(options: LifecycleOptions): void {
122
- if (options.deadlineMs !== undefined) deadlineMs = options.deadlineMs;
125
+ // Screened above the write, never beside the arithmetic: `Math.max(0, deadlineAt - monotonic())`
126
+ // PROPAGATES a NaN into `setTimeout(fn, NaN)`, i.e. 0 — measured, one in-flight operation dropped
127
+ // and a 300ms close hook ABANDONED 111ms into a 25s budget, while `X_SHUTDOWN_TIMEOUT` rendered
128
+ // `NaNms` and told the operator to RAISE a budget that was never a number. `min: 0` because 0 is
129
+ // a real budget — drain now, no grace — and `@ultimat3/http`'s `drainTimeoutMs` accepts 0 and
130
+ // hands it straight here, so a floor of 1 would refuse at boot what that package declares.
131
+ if (options.deadlineMs !== undefined) {
132
+ deadlineMs = finiteCount('configureLifecycle', 'deadlineMs', options.deadlineMs, 0);
133
+ }
123
134
  if (options.clock !== undefined) {
124
135
  clock = options.clock;
125
136
  startedAtMono = clock.monotonic();
@@ -0,0 +1,68 @@
1
+ // Single responsibility: the identifier grammar every metric name and every LABEL name obeys, and
2
+ // the one code that refuses a value outside it. Split out of `metrics.ts` because that file is at
3
+ // the 500-line ceiling `x verify`'s `filesize` step enforces, and because the two refusals are one
4
+ // rule asked of two things — the exposition format spells them identically.
5
+
6
+ import { type CodedErrorInit, UltimateError } from './errors';
7
+ import type { MetricAttributes } from './metrics-types';
8
+
9
+ export class MetricNameInvalidError extends UltimateError {
10
+ static readonly code = 'X_METRIC_NAME_INVALID';
11
+ override readonly name = 'MetricNameInvalidError';
12
+ constructor(init: CodedErrorInit) {
13
+ super({ ...init, code: MetricNameInvalidError.code });
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Lowercase snake, the intersection of what every exposition format accepts. OTel's dotted names
19
+ * survive an OTLP hop but not a Prometheus scrape, and the autoscaler reads the scrape.
20
+ */
21
+ export const METRIC_NAME_RE = /^[a-z_][a-z0-9_]*$/;
22
+
23
+ /**
24
+ * The suggestion both `fix:` lines below hand back, and it has to SATISFY `METRIC_NAME_RE`, not
25
+ * merely resemble it: the grammar's first character is `[a-z_]`, so lowercasing `2digits` left
26
+ * `2digits` and the refusal told the caller to rename the label to the same name it had just
27
+ * refused. An underscore prefix is the one repair that keeps the rest of the identifier — which is
28
+ * also what makes the empty string a nameable `_` rather than a second refusal.
29
+ */
30
+ const snake = (value: string): string => {
31
+ const lowered = value.toLowerCase().replaceAll(/[^a-z0-9_]/g, '_');
32
+ return /^[a-z_]/.test(lowered) ? lowered : `_${lowered}`;
33
+ };
34
+
35
+ /** Refused at DECLARATION, where the instrument is still nameable. */
36
+ export function assertMetricName(name: string): void {
37
+ if (METRIC_NAME_RE.test(name)) return;
38
+ throw new MetricNameInvalidError({
39
+ cause: `"${name}" is not lowercase snake_case matching ${METRIC_NAME_RE.source}`,
40
+ fix: `rename the instrument to lowercase snake_case, e.g. ${snake(name)}`,
41
+ meta: { name },
42
+ });
43
+ }
44
+
45
+ /**
46
+ * A LABEL name obeys that same grammar, and it was guarded nowhere: `metrics-text.ts` escapes the
47
+ * label VALUE and interpolates the KEY raw, so `bad"key` rendered `orders_total{bad"key="x"} 1`
48
+ * and a `\n` in one split the body into a line the scraper reads as a different series. A scraper
49
+ * rejects the WHOLE response over one unparseable line, so `http_requests_total`, `connections`
50
+ * and `queue_depth` — the three the chart's HPA reads — disappear together, and the autoscaler
51
+ * sees no signal rather than a bad one.
52
+ *
53
+ * Refused on the way in, never sanitised at render: a silently renamed label is a different
54
+ * series, and the point would land somewhere the dashboard querying the declared name cannot see.
55
+ * Once per label set the instrument has not seen — `seriesFor` calls this on its MISS, ahead of
56
+ * the cardinality ceiling, because the overflow branch answers without allocating a series and a
57
+ * check hung off the allocation would have made the refusal a function of how busy the process is.
58
+ */
59
+ export function assertLabelNames(metric: string, attributes: MetricAttributes): void {
60
+ for (const label of Object.keys(attributes)) {
61
+ if (METRIC_NAME_RE.test(label)) continue;
62
+ throw new MetricNameInvalidError({
63
+ cause: `${metric} was given the label "${label}", which is not lowercase snake_case matching ${METRIC_NAME_RE.source} — the exposition line it renders is one no scraper can parse, and one bad line drops the whole scrape`,
64
+ fix: `rename the label at the call site to lowercase snake_case, e.g. { ${snake(label)}: … }`,
65
+ meta: { metric, label },
66
+ });
67
+ }
68
+ }
@@ -14,6 +14,12 @@ function escapeLabel(value: string): string {
14
14
  return value.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('\n', '\\n');
15
15
  }
16
16
 
17
+ /**
18
+ * The KEY is interpolated bare and the VALUE is escaped, which is the whole grammar: a label name
19
+ * is an identifier `metrics.ts` refuses at series creation (`X_METRIC_NAME_INVALID`), a label
20
+ * value is arbitrary text. Never sanitise a key here — a renamed label is a different series, and
21
+ * this function has no way to tell the dashboard that.
22
+ */
17
23
  function labels(attributes: MetricAttributes, extra?: readonly [string, string]): string {
18
24
  const pairs = Object.entries(attributes)
19
25
  .sort(([a], [b]) => (a < b ? -1 : 1))
package/src/metrics.ts CHANGED
@@ -2,10 +2,12 @@
2
2
  // aggregated in process, read through one `collectMetrics()`. Shaped exactly like `telemetry.ts`:
3
3
  // always on, a no-op exporter by default, and the wire format supplied by a driver, never here.
4
4
 
5
+ import { assert } from './assert';
5
6
  import { type Clock, systemClock } from './clock';
6
7
  import { renderThrowable } from './error-render';
7
8
  import { type CodedErrorInit, UltimateError } from './errors';
8
9
  import { logger } from './logger';
10
+ import { assertLabelNames, assertMetricName, MetricNameInvalidError } from './metric-names';
9
11
  import type {
10
12
  Counter,
11
13
  Gauge,
@@ -22,8 +24,10 @@ import type {
22
24
  } from './metrics-types';
23
25
  import { serviceResource } from './telemetry';
24
26
 
25
- // The data model is a module of its own; the public surface is unchanged, so nothing that imports
26
- // a metric type from here has to learn a second path.
27
+ // The data model and the identifier grammar are modules of their own; the public surface is
28
+ // unchanged, so nothing that imports a metric type or the name error from here learns a second
29
+ // path.
30
+ export { MetricNameInvalidError } from './metric-names';
27
31
  export type {
28
32
  Counter,
29
33
  Gauge,
@@ -42,14 +46,6 @@ export type {
42
46
  ReadableMetric,
43
47
  } from './metrics-types';
44
48
 
45
- export class MetricNameInvalidError extends UltimateError {
46
- static readonly code = 'X_METRIC_NAME_INVALID';
47
- override readonly name = 'MetricNameInvalidError';
48
- constructor(init: CodedErrorInit) {
49
- super({ ...init, code: MetricNameInvalidError.code });
50
- }
51
- }
52
-
53
49
  export class MetricValueInvalidError extends UltimateError {
54
50
  static readonly code = 'X_METRIC_VALUE_INVALID';
55
51
  override readonly name = 'MetricValueInvalidError';
@@ -88,12 +84,6 @@ export const DEFAULT_HISTOGRAM_BOUNDS: readonly number[] = Object.freeze([
88
84
  0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10,
89
85
  ]);
90
86
 
91
- /**
92
- * Lowercase snake, the intersection of what every exposition format accepts. OTel's dotted names
93
- * survive an OTLP hop but not a Prometheus scrape, and the autoscaler reads the scrape.
94
- */
95
- const METRIC_NAME_RE = /^[a-z_][a-z0-9_]*$/;
96
-
97
87
  export const noopMetricExporter: MetricExporter = Object.freeze({
98
88
  export(): void {
99
89
  // Intentionally empty: instruments are always live, and free until an exporter is configured.
@@ -231,13 +221,7 @@ function assertBounds(name: string, bounds: readonly number[] | undefined): void
231
221
  }
232
222
 
233
223
  function declare(name: string, kind: MetricKind, options: GaugeOptions & HistogramOptions) {
234
- if (!METRIC_NAME_RE.test(name)) {
235
- throw new MetricNameInvalidError({
236
- cause: `"${name}" is not lowercase snake_case matching ${METRIC_NAME_RE.source}`,
237
- fix: `rename the instrument to lowercase snake_case, e.g. ${name.toLowerCase().replaceAll(/[^a-z0-9_]/g, '_')}`,
238
- meta: { name },
239
- });
240
- }
224
+ assertMetricName(name);
241
225
  assertBounds(name, options.bounds);
242
226
  const existing = instruments.get(name);
243
227
  if (existing !== undefined) {
@@ -367,6 +351,12 @@ function seriesFor(instrument: Instrument, attributes: MetricAttributes): Series
367
351
  const key = seriesKey(attributes);
368
352
  const found = instrument.series.get(key);
369
353
  if (found !== undefined) return found;
354
+ // On the MISS, ahead of the ceiling — never inside `createSeries`, which the overflow branch
355
+ // below returns without reaching. A screen that ran only where a series is BORN was a screen
356
+ // that depended on load: `bad"key` threw on a fresh process and was swallowed on a busy one,
357
+ // once the instrument had filled up, which is exactly when an unparseable label is likeliest to
358
+ // arrive. A hit needs no check — a key in the map passed this on the way in.
359
+ assertLabelNames(instrument.descriptor.name, attributes);
370
360
  if (instrument.series.size >= instrument.maxSeries) {
371
361
  reportOverflow(instrument);
372
362
  // Created directly rather than through this function again: the overflow series is the ONE
@@ -485,6 +475,14 @@ export function exportMetrics(): void {
485
475
  * — the drain hook, not this timer, decides when the process may leave.
486
476
  */
487
477
  export function startMetricExport(intervalMs = 60_000): () => void {
478
+ // A timer given a non-finite delay does not export less often — `setInterval(fn, NaN)` runs at
479
+ // 1ms in this Bun, measured, so a `Number(process.env.METRICS_INTERVAL_MS)` on an unset variable
480
+ // pushes a thousand collections a second for the life of the process.
481
+ assert(
482
+ Number.isSafeInteger(intervalMs) && intervalMs >= 1,
483
+ `startMetricExport was given ${String(intervalMs)}ms; an export interval is a whole number of at least 1ms, and a timer given anything else runs at 1ms`,
484
+ "pass a whole number of milliseconds — startMetricExport(60_000) — and parse an environment value first: Number(process.env.METRICS_INTERVAL_MS ?? '') is NaN when the variable is unset",
485
+ );
488
486
  const timer = setInterval(exportMetrics, intervalMs);
489
487
  timer.unref();
490
488
  return () => clearInterval(timer);
@@ -11,6 +11,7 @@ import type {
11
11
  ReadableMetric,
12
12
  } from './metrics';
13
13
  import {
14
+ assertFiniteOtlpBound,
14
15
  OTLP_SCOPE,
15
16
  otlpAttributes,
16
17
  otlpEndpoint,
@@ -118,7 +119,7 @@ export interface OtlpMetricExporter extends MetricExporter {
118
119
  export function otlpMetricExporter(options: OtlpMetricExporterOptions = {}): OtlpMetricExporter {
119
120
  const url = otlpEndpoint('metrics', options.endpoint);
120
121
  const headers = otlpHeaders(options.headers);
121
- const timeoutMs = options.timeoutMs ?? 10_000;
122
+ const timeoutMs = assertFiniteOtlpBound('timeoutMs', options.timeoutMs ?? 10_000);
122
123
  const send = options.fetch ?? globalThis.fetch;
123
124
  let startedAtMs = options.startedAtMs;
124
125
  let inflight: Promise<void> = Promise.resolve();
@@ -4,6 +4,7 @@
4
4
  import { renderThrowable } from './error-render';
5
5
  import { logger } from './logger';
6
6
  import {
7
+ assertFiniteOtlpBound,
7
8
  OTLP_SCOPE,
8
9
  type OtlpKeyValue,
9
10
  otlpAttributes,
@@ -126,9 +127,13 @@ export interface OtlpSpanExporter extends SpanExporter {
126
127
  export function otlpSpanExporter(options: OtlpSpanExporterOptions = {}): OtlpSpanExporter {
127
128
  const url = otlpEndpoint('traces', options.endpoint);
128
129
  const headers = otlpHeaders(options.headers);
129
- const maxBatchSize = options.maxBatchSize ?? 512;
130
- const maxQueueSize = options.maxQueueSize ?? 2048;
131
- const timeoutMs = options.timeoutMs ?? 10_000;
130
+ const maxBatchSize = assertFiniteOtlpBound('maxBatchSize', options.maxBatchSize ?? 512);
131
+ const maxQueueSize = assertFiniteOtlpBound('maxQueueSize', options.maxQueueSize ?? 2048);
132
+ const timeoutMs = assertFiniteOtlpBound('timeoutMs', options.timeoutMs ?? 10_000);
133
+ const flushIntervalMs = assertFiniteOtlpBound(
134
+ 'flushIntervalMs',
135
+ options.flushIntervalMs ?? 5_000,
136
+ );
132
137
  const send = options.fetch ?? globalThis.fetch;
133
138
  const queue: ReadableSpan[] = [];
134
139
  let inflight: Promise<void> = Promise.resolve();
@@ -173,7 +178,7 @@ export function otlpSpanExporter(options: OtlpSpanExporterOptions = {}): OtlpSpa
173
178
 
174
179
  // Unref'd, so a pending flush never holds a draining process open — `shutdown()` is what
175
180
  // decides the last batch leaves, exactly as `startMetricExport` defers to the drain hook.
176
- const timer = setInterval(() => void drainQueue(), options.flushIntervalMs ?? 5_000);
181
+ const timer = setInterval(() => void drainQueue(), flushIntervalMs);
177
182
  timer.unref();
178
183
 
179
184
  return {
package/src/otlp.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  // an operator already sets, header parsing, the OTLP/JSON value encoding, and one POST that never
3
3
  // throws. A serialisation, not a vendor (axiom 7), exactly as `metrics-text.ts` is to Prometheus.
4
4
 
5
+ import { assert } from './assert';
5
6
  import { renderThrowable } from './error-render';
6
7
  import { type CodedErrorInit, UltimateError } from './errors';
7
8
  import { logger } from './logger';
@@ -111,6 +112,29 @@ export function tryOtlpEndpoint(
111
112
  }
112
113
 
113
114
  /** The endpoint, or a coded error naming the variable to set. */
115
+ /**
116
+ * A batch size, a queue bound or a millisecond budget, screened where the exporter is CONSTRUCTED.
117
+ *
118
+ * These arrive as `Number(process.env.OTEL_…)` as often as literals, and `NaN` is not nullish so
119
+ * `??` keeps it. What follows is never a wrong number: `setInterval(fn, NaN)` runs at 1ms in this
120
+ * Bun (measured, with a `TimeoutNaNWarning`), so the collector is POSTed a thousand times a
121
+ * second; `AbortSignal.timeout(NaN)` throws, and `postOtlp` swallows it, so every export fails
122
+ * with one warn line and the traces simply stop; and `queue.length >= NaN` is false, so a bound
123
+ * that exists to be a bound stops being one. `X_INVARIANT` rather than a fourth `X_OTLP_*` code:
124
+ * the three here name the endpoint, the headers and the protocol, and this is none of those.
125
+ *
126
+ * The `Finite` in the name is load-bearing: `bun run finite-bounds` recognises a repair by the
127
+ * shape of the CALL, so a screen named for its subject alone is invisible to the ratchet.
128
+ */
129
+ export function assertFiniteOtlpBound(option: string, value: number): number {
130
+ assert(
131
+ Number.isSafeInteger(value) && value >= 1,
132
+ `otlp exporter option ${option} is ${String(value)}; it must be a whole number of at least 1 — a comparison against a non-finite bound is false for every value, and a timer given one runs at 1ms`,
133
+ `pass a whole number for ${option}, and parse an environment value before you pass it: Number(process.env.OTEL_BSP_SCHEDULE_DELAY ?? '') is NaN when the variable is unset`,
134
+ );
135
+ return value;
136
+ }
137
+
114
138
  export function otlpEndpoint(
115
139
  signal: OtlpSignal,
116
140
  explicit?: string | undefined,
@@ -4,6 +4,8 @@
4
4
  // `@ultimat3/mcp` (tier 4) serves a bare `Request` that never passes through http's pipeline —
5
5
  // `await request.json()` there was governed only by Bun's 128 MiB default.
6
6
 
7
+ import { assert } from './assert';
8
+
7
9
  /** What a body read produced: the bytes, or the running total at the moment it went over. */
8
10
  export type CappedBody = { readonly bytes: Uint8Array } | { readonly over: number };
9
11
 
@@ -17,6 +19,18 @@ export const readWithinLimit = async (
17
19
  body: ReadableStream<Uint8Array> | null,
18
20
  limit: number,
19
21
  ): Promise<CappedBody> => {
22
+ // The cap is this function's whole contract, so it is screened here and not by each caller in
23
+ // turn. `total > NaN` is false for every chunk, so a non-finite limit does not RAISE the ceiling
24
+ // — it removes it, and the payload the caller was promised would never be held in full is held
25
+ // in full. Both callers reach it from configuration (`@ultimat3/http`'s `bodyLimitBytes`,
26
+ // `@ultimat3/mcp`'s own), and `Number(process.env.…)` on an unset variable is `NaN`. Before the
27
+ // body is touched: a cap this wrong is the caller's bug, not the peer's, and cancelling their
28
+ // stream over it would be answering the wrong party.
29
+ assert(
30
+ Number.isSafeInteger(limit) && limit >= 0,
31
+ `readWithinLimit was given a limit of ${String(limit)}; a byte cap is a whole number of zero or more, and every comparison against a non-finite one is false, so the read is unbounded rather than capped`,
32
+ "pass a whole number of bytes — readWithinLimit(request.body, 1_048_576) — and parse an environment value before you pass it: Number(process.env.BODY_LIMIT_BYTES ?? '') is NaN when the variable is unset",
33
+ );
20
34
  if (body === null) return { bytes: new Uint8Array(0) };
21
35
  const reader = body.getReader();
22
36
  const chunks: Uint8Array[] = [];
@@ -26,15 +40,19 @@ export const readWithinLimit = async (
26
40
  const { done, value } = await reader.read();
27
41
  if (done) break;
28
42
  total += value.byteLength;
29
- if (total > limit) {
30
- // Cancelled rather than drained: the peer is told to stop sending, and nothing past the
31
- // cap is ever held. Draining is how a rejected request still costs its full transfer.
32
- await reader.cancel();
33
- return { over: total };
34
- }
43
+ // Cancelled rather than drained, in the `finally` below: the peer is told to stop sending,
44
+ // and nothing past the cap is ever held. Draining is how a rejected request still costs its
45
+ // full transfer.
46
+ if (total > limit) return { over: total };
35
47
  chunks.push(value);
36
48
  }
37
49
  } finally {
50
+ // Cancel rather than merely release — `@ultimat3/storage`'s `readWithin` is the same bounded
51
+ // read and states the same rule: a refused body must stop arriving, not keep filling a socket
52
+ // buffer nobody will read. Swallowed, because a cancellation that fails on the way out must
53
+ // not replace the answer this function computed: an unguarded `cancel()` turned the 413 into
54
+ // whatever the stream threw. A completed stream answers it as a no-op.
55
+ await reader.cancel().catch(() => undefined);
38
56
  reader.releaseLock();
39
57
  }
40
58
  const bytes = new Uint8Array(total);
package/src/sampler.ts CHANGED
@@ -9,9 +9,20 @@ import type { SpanAttributes, SpanContext } from './telemetry';
9
9
  * The seam. `parent` is the inbound span context when there is one — an upstream that decided
10
10
  * "not sampled" has said so in `parent.traceFlags`, and a sampler that ignores it splits one
11
11
  * distributed trace into a sampled half and an unsampled half, which is worse than either.
12
+ *
13
+ * `traceId` is the id of the span being CREATED — the parent's carried forward, or the one this
14
+ * process just minted for a root. It is an argument of its own for the same reason OTel makes it
15
+ * one: a trace-id-hashing sampler has to answer for a ROOT too, where there is no parent to read
16
+ * the id off, and every span of one trace has to reach the same answer or the collector is handed
17
+ * orphans. An implementation that ignores it (`always_on`, `parentbased_*`) simply omits it.
12
18
  */
13
19
  export interface Sampler {
14
- shouldSample(name: string, parent: SpanContext | undefined, attributes: SpanAttributes): boolean;
20
+ shouldSample(
21
+ name: string,
22
+ parent: SpanContext | undefined,
23
+ attributes: SpanAttributes,
24
+ traceId: string,
25
+ ): boolean;
15
26
  }
16
27
 
17
28
  export const OTEL_SAMPLER_KEY = 'OTEL_TRACES_SAMPLER';
@@ -55,13 +66,38 @@ export function parentBasedRatioSampler(
55
66
  };
56
67
  }
57
68
 
58
- /** `always_off` / `always_on` without the parent-based prefix ignore the inbound decision. */
69
+ /** The 64-bit space OTel's `TraceIdRatioBased` compares the low 8 bytes of a trace id against. */
70
+ const TRACE_ID_SPACE = 2 ** 64;
71
+
72
+ const TRACE_ID_HEX = /^[0-9a-f]{32}$/;
73
+
74
+ /**
75
+ * OTel's `TraceIdRatioBased`: the LOW 8 bytes of the trace id as a big-endian unsigned integer,
76
+ * against `ratio` of the 2^64 space. Answers `undefined` for anything that is not a 32-hex trace
77
+ * id, so the caller decides what to do with an id no hash can read rather than this silently
78
+ * sampling or silently dropping it.
79
+ */
80
+ function traceIdSampled(traceId: string, ratio: number): boolean | undefined {
81
+ if (!TRACE_ID_HEX.test(traceId)) return undefined;
82
+ return BigInt(`0x${traceId.slice(16)}`) < BigInt(Math.floor(ratio * TRACE_ID_SPACE));
83
+ }
84
+
85
+ /**
86
+ * `traceidratio` — the OTel spelling that ignores the inbound decision and hashes the TRACE ID.
87
+ *
88
+ * The hash is the whole of it, and a roll is not a substitute: `startSpan` asks the sampler for
89
+ * every span, root and child alike, so a `random()` here re-decided per span and one trace left
90
+ * as a sampled half and an unsampled half — measured at 0.5, an unexported root with eight of its
91
+ * twenty children at the collector. Hashing the id makes the answer a function of the trace, which
92
+ * is what the name of this sampler means and what lets a second SERVICE agree with us unprompted.
93
+ * `random` survives only for an id no hash can read, and stays injectable so that path is a test.
94
+ */
59
95
  export function ratioSampler(ratio: number, random: () => number = Math.random): Sampler {
60
96
  return {
61
- shouldSample(): boolean {
97
+ shouldSample(_name, _parent, _attributes, traceId): boolean {
62
98
  if (ratio >= 1) return true;
63
99
  if (ratio <= 0) return false;
64
- return random() < ratio;
100
+ return traceIdSampled(traceId, ratio) ?? random() < ratio;
65
101
  },
66
102
  };
67
103
  }
package/src/telemetry.ts CHANGED
@@ -201,10 +201,13 @@ export function startSpan(name: string, options?: StartSpanOptions): Span {
201
201
  // so one trace is sampled or not sampled as a whole. Before this, `traceFlags` was hardcoded to
202
202
  // 1 for a root and `end()` exported regardless, which made the bit a value the framework
203
203
  // forwarded and nobody obeyed.
204
+ // The id is resolved BEFORE the decision, because the decision may be a function of it: a
205
+ // `traceidratio` sampler hashes the trace id, and a root's id exists nowhere else to hash.
206
+ const traceId = parent?.traceId ?? newTraceId();
204
207
  const context: SpanContext = {
205
- traceId: parent?.traceId ?? newTraceId(),
208
+ traceId,
206
209
  spanId: newSpanId(),
207
- traceFlags: currentSampler().shouldSample(name, inbound, attributes) ? 1 : 0,
210
+ traceFlags: currentSampler().shouldSample(name, inbound, attributes, traceId) ? 1 : 0,
208
211
  };
209
212
  const events: SpanEvent[] = [];
210
213
  const startedAt = clock.now().getTime();