@ultimat3/core 16.0.0 → 18.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/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
  }
@@ -1,32 +1,33 @@
1
1
  // Single responsibility: register `@ultimat3/schema`'s error codes so their titles render for any
2
2
  // process that imports `@ultimat3/core` — not just the CLI. `@ultimat3/schema` is tier 0 alongside
3
- // this package, so it cannot call `registerErrorCodes()` itself (that would mean importing core,
4
- // a same-tier import) and this package cannot import schema to read its declarations back (same
5
- // reason, the other direction). The codes below are a deliberate, tested duplicate of
6
- // `SCHEMA_ERROR_CODES` in `packages/schema/src/errors.ts` — `schema-error-codes-pin.test.ts`, in a
7
- // package that may legally import both (`@ultimat3/cli`), asserts them equal so a title edited in
8
- // one place and not the other fails the build instead of quietly disagreeing at runtime.
3
+ // this package and cannot call `registerErrorCodes()` itself: that would be `schema -> core`, an
4
+ // import this package's own dependency-free promise forbids in that direction and always will.
5
+ // The other direction is DECLARED (`core -> schema`, `scripts/lib/tiers.ts`), so the titles are
6
+ // READ from schema rather than restated here.
7
+ //
8
+ // They were a hand-kept duplicate until 2026-08-27, held equal by `schema-error-codes-pin.test.ts`
9
+ // in `@ultimat3/cli` — a tier-5 package pinning a tier-0 invariant, which nothing required to
10
+ // exist. It is deleted with this change.
9
11
 
12
+ import { SCHEMA_ERROR_CODES } from '@ultimat3/schema';
10
13
  import { registerErrorCodes } from './error-codes';
11
14
  import { registerErrorRetry } from './error-retry';
12
15
 
13
- /** Mirrors `SCHEMA_ERROR_CODES` in `packages/schema/src/errors.ts`. Keep the titles identical. */
14
- export const SCHEMA_ERROR_CODE_TITLES: Readonly<Record<string, string>> = Object.freeze({
15
- X_VALIDATION_FAILED: 'value did not match its schema',
16
- X_SCHEMA_UNSUPPORTED: 'the active schema provider cannot do this',
17
- X_SCHEMA_DISCRIMINANT_INVALID: 'a discriminated union member can never be dispatched to',
18
- X_SCHEMA_DEFAULT_UNSHAREABLE: 'a schema default cannot be copied per parse',
19
- });
16
+ /**
17
+ * Schema's own declarations, projected to titles. Kept as an export because it is public API
18
+ * shipped since 1.0; it is now DERIVED and can no longer disagree with its source.
19
+ */
20
+ export const SCHEMA_ERROR_CODE_TITLES: Readonly<Record<string, string>> = Object.freeze(
21
+ Object.fromEntries(
22
+ Object.entries(SCHEMA_ERROR_CODES).map(([code, declared]) => [code, declared.title]),
23
+ ),
24
+ );
20
25
 
21
26
  // Registered here rather than in `error-codes.ts`'s `CORE_CODE_TITLES` because core does not own
22
27
  // these codes — `@ultimat3/schema` does — and `registerErrorCodes` is the one mechanism that
23
28
  // raises `X_ERROR_CODE_DUPLICATE` if a package that DOES own one of them ever tries to register it
24
- // too, which pins ownership even though the titles live in two files.
25
- registerErrorCodes(
26
- Object.fromEntries(
27
- Object.entries(SCHEMA_ERROR_CODE_TITLES).map(([code, title]) => [code, { title }]),
28
- ),
29
- );
29
+ // too, which pins ownership even though the registration happens here.
30
+ registerErrorCodes(SCHEMA_ERROR_CODES);
30
31
 
31
32
  // And how each is RETRIED, here for the same tier reason the titles are here.
32
33
  //
@@ -41,9 +42,10 @@ registerErrorCodes(
41
42
  // `packages/scraping/src/cdp-target.ts` names the gap and says it cannot be closed from there.
42
43
  //
43
44
  // A value that does not match its schema does not match it on attempt five either.
44
- registerErrorRetry({
45
- X_VALIDATION_FAILED: 'terminal',
46
- X_SCHEMA_UNSUPPORTED: 'terminal',
47
- X_SCHEMA_DISCRIMINANT_INVALID: 'terminal',
48
- X_SCHEMA_DEFAULT_UNSHAREABLE: 'terminal',
49
- });
45
+ //
46
+ // DERIVED from the same set, so a code schema adds cannot arrive unclassified: the list was typed
47
+ // out here and a fifth code would have been silently missing from it, which is precisely the
48
+ // "unregistered reads as unclassified" failure the paragraph above describes.
49
+ registerErrorRetry(
50
+ Object.fromEntries(Object.keys(SCHEMA_ERROR_CODES).map((code) => [code, 'terminal' as const])),
51
+ );
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();
@@ -1,43 +1,14 @@
1
- // Single responsibility: is a string an IANA zone NAME? Tier 0's statement of the one rule
2
- // `@ultimat3/time` enforces everywhere above it — a zone is `Area/Location`, and `UTC` is the one
3
- // exception. It is stated twice because `core` is tier 0 and may not import `@ultimat3/time`;
4
- // `packages/time/src/zone-canonical.ts` is where the rule and its reasoning are written down.
1
+ // Single responsibility: re-export `@ultimat3/schema`'s IANA zone-name predicate, so `app.config.ts`
2
+ // and `t.timezone` judge a zone with ONE function.
3
+ //
4
+ // This file held a character-for-character copy of it until 2026-08-27 — `core` and `schema` are
5
+ // both tier 0 and neither could import the other — kept equal by a 123-line pin test in
6
+ // `@ultimat3/cli`, a tier-5 package pinning a tier-0 invariant that no rule required to exist.
7
+ // `core -> schema` is now a declared edge (`scripts/lib/tiers.ts`), so the copy is gone.
8
+ //
9
+ // The rule itself, and why a bare `new Intl.DateTimeFormat(…)` probe is not it — ICU 78 resolves
10
+ // `CET`, `EST`, `Japan`, `GMT` and `Zulu`, so `defaultTimeZone: 'CET'` passed validation at boot
11
+ // and threw `X_TIMEZONE_INVALID` on the first `format` call (issue #257) — is written down in
12
+ // `packages/schema/src/time-zone-name.ts` and in `packages/time/src/zone-canonical.ts`.
5
13
 
6
- /**
7
- * A LEADING sign is a fixed offset, which carries no DST rules. `Etc/GMT+2` keeps its `+`.
8
- *
9
- * Unobservable on ICU 78 — `+01:00` resolves to itself, so the slash rule below already refuses it,
10
- * and deleting this line changes no answer this package can currently produce. It stays because it
11
- * guards the runtime that folds an offset into `Etc/GMT-1`, which WOULD carry a slash, and because
12
- * `packages/time/src/zone-canonical.ts` carries the same line: two statements of one rule may not
13
- * differ, least of all in the half that is hard to test.
14
- */
15
- const NUMERIC_OFFSET = /^[+-]/;
16
-
17
- /**
18
- * Structural, and never delegated to `Intl` — the reasoning, and why a denylist is not the
19
- * alternative, is `packages/time/src/zone-canonical.ts`'s and is not re-derived here. What this
20
- * file enforces is that same rule for `app.config.ts`: an identifier is `Area/Location`, `UTC` is
21
- * the one legal exception, and a leading sign is an offset rather than a name.
22
- *
23
- * It exists because a bare `new Intl.DateTimeFormat(…)` probe was the second answer to that
24
- * question, and the two stopped agreeing: ICU 78 (Bun 1.4) resolves `CET`, `EST`, `Japan`, `GMT`,
25
- * `Zulu` and the whole `backward`/abbreviation family, so `defaultTimeZone: 'CET'` passed validation
26
- * at boot and threw `X_TIMEZONE_INVALID` on the first `format` call — a config file accepting a
27
- * value nothing downstream can use (issue #257).
28
- *
29
- * The judgement is made on the RESOLVED name, not the input, so a casing (`utc`) and a runtime that
30
- * folds an alias into its target (`Etc/UTC` → `UTC`) both answer correctly. No cache: this is read
31
- * once per process at config validation, never off a request header — a zone that arrives from a
32
- * caller goes through `@ultimat3/time`'s `canonicalTimeZone`, which is bounded and canonicalizing.
33
- */
34
- export function isIanaZoneName(value: string): boolean {
35
- if (value === '' || NUMERIC_OFFSET.test(value)) return false;
36
- try {
37
- const resolved = new Intl.DateTimeFormat('en-US', { timeZone: value }).resolvedOptions()
38
- .timeZone;
39
- return resolved === 'UTC' || resolved.includes('/');
40
- } catch {
41
- return false;
42
- }
43
- }
14
+ export { isIanaZoneName } from '@ultimat3/schema';